summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKamil Trzcinski <ayufan@ayufan.eu>2016-07-19 23:33:42 +0200
committerKamil Trzcinski <ayufan@ayufan.eu>2016-07-19 23:33:42 +0200
commitbf9db45a1dc4d8269fa68dc27dac6667c89ffda8 (patch)
tree813d6c8151c156d186cdf4e3420f42260d206434
parent0aedeb5637932fa827e42be7441e9c967049dd1d (diff)
parentb9ed9d658ad447a64d58b2040849a7cc0e698287 (diff)
downloadgitlab-ce-bf9db45a1dc4d8269fa68dc27dac6667c89ffda8.tar.gz
Merge remote-tracking branch 'origin/master' into ci-predefined-variables
-rw-r--r--.gitlab-ci.yml6
-rw-r--r--.hound.yml4
-rw-r--r--.rubocop.yml375
-rw-r--r--.rubocop_todo.yml462
-rw-r--r--.teatro.yml8
-rw-r--r--CHANGELOG294
-rw-r--r--CONTRIBUTING.md3
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--Gemfile142
-rw-r--r--Gemfile.lock300
-rw-r--r--MAINTENANCE.md36
-rw-r--r--VERSION2
-rw-r--r--app/assets/images/auth_buttons/azure_64.pngbin986 -> 695 bytes
-rw-r--r--app/assets/images/auth_buttons/bitbucket_64.pngbin2163 -> 2161 bytes
-rw-r--r--app/assets/images/auth_buttons/facebook_64.pngbin2970 -> 870 bytes
-rw-r--r--app/assets/images/auth_buttons/github_64.pngbin2625 -> 1151 bytes
-rw-r--r--app/assets/images/auth_buttons/gitlab_64.pngbin2849 -> 2070 bytes
-rw-r--r--app/assets/images/auth_buttons/google_64.pngbin5281 -> 4366 bytes
-rw-r--r--app/assets/images/auth_buttons/twitter_64.pngbin4835 -> 3110 bytes
-rw-r--r--app/assets/images/bg_fallback.pngbin167 -> 167 bytes
-rw-r--r--app/assets/images/dark-scheme-preview.pngbin3996 -> 3992 bytes
-rw-r--r--app/assets/images/emoji.pngbin263533 -> 1087659 bytes
-rw-r--r--app/assets/images/emoji@2x.pngbin690504 -> 2652225 bytes
-rw-r--r--app/assets/images/gitlab_logo.pngbin5189 -> 3616 bytes
-rw-r--r--app/assets/images/gitorious-logo-black.pngbin809 -> 631 bytes
-rw-r--r--app/assets/images/gitorious-logo-blue.pngbin495 -> 201 bytes
-rw-r--r--app/assets/images/icon-link.pngbin1128 -> 729 bytes
-rw-r--r--app/assets/images/images.pngbin5849 -> 5806 bytes
-rw-r--r--app/assets/images/monokai-scheme-preview.pngbin3711 -> 3708 bytes
-rw-r--r--app/assets/images/msapplication-tile.pngbin5798 -> 4328 bytes
-rw-r--r--app/assets/images/no_avatar.pngbin621 -> 621 bytes
-rw-r--r--app/assets/images/no_group_avatar.pngbin942 -> 939 bytes
-rw-r--r--app/assets/images/slider_handles.pngbin1377 -> 1341 bytes
-rw-r--r--app/assets/images/touch-icon-ipad-retina.pngbin8130 -> 5662 bytes
-rw-r--r--app/assets/images/touch-icon-ipad.pngbin3493 -> 2465 bytes
-rw-r--r--app/assets/images/touch-icon-iphone-retina.pngbin4997 -> 3460 bytes
-rw-r--r--app/assets/images/touch-icon-iphone.pngbin2766 -> 1949 bytes
-rw-r--r--app/assets/javascripts/api.js.coffee9
-rw-r--r--app/assets/javascripts/application.js.coffee88
-rw-r--r--app/assets/javascripts/awards_handler.coffee6
-rw-r--r--app/assets/javascripts/blob/blob_ci_yaml.js.coffee23
-rw-r--r--app/assets/javascripts/blob/edit_blob.js.coffee1
-rw-r--r--app/assets/javascripts/blob/template_selector.js.coffee4
-rw-r--r--app/assets/javascripts/build.coffee (renamed from app/assets/javascripts/ci/build.coffee)14
-rw-r--r--app/assets/javascripts/ci/application.js.coffee12
-rw-r--r--app/assets/javascripts/ci/projects.js.coffee3
-rw-r--r--app/assets/javascripts/compare_autocomplete.js.coffee41
-rw-r--r--app/assets/javascripts/diff.js.coffee5
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee7
-rw-r--r--app/assets/javascripts/dropzone_input.js.coffee4
-rw-r--r--app/assets/javascripts/files_comment_button.js.coffee97
-rw-r--r--app/assets/javascripts/flash.js.coffee32
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js.coffee9
-rw-r--r--app/assets/javascripts/gl_dropdown.js.coffee67
-rw-r--r--app/assets/javascripts/gl_form.js.coffee3
-rw-r--r--app/assets/javascripts/graphs/application.js.coffee1
-rw-r--r--app/assets/javascripts/graphs/stat_graph_contributors_graph.js.coffee6
-rw-r--r--app/assets/javascripts/importer_status.js.coffee11
-rw-r--r--app/assets/javascripts/issuable.js.coffee23
-rw-r--r--app/assets/javascripts/issue.js.coffee2
-rw-r--r--app/assets/javascripts/issue_status_select.js.coffee7
-rw-r--r--app/assets/javascripts/issues-bulk-assignment.js.coffee31
-rw-r--r--app/assets/javascripts/labels_select.js.coffee17
-rw-r--r--app/assets/javascripts/layout_nav.js.coffee9
-rw-r--r--app/assets/javascripts/lib/chart.js.coffee1
-rw-r--r--app/assets/javascripts/lib/cropper.js.coffee1
-rw-r--r--app/assets/javascripts/lib/d3.js.coffee1
-rw-r--r--app/assets/javascripts/lib/raphael.js.coffee3
-rw-r--r--app/assets/javascripts/lib/utils/animate.js.coffee (renamed from app/assets/javascripts/lib/animate.js.coffee)0
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js.coffee (renamed from app/assets/javascripts/lib/common_utils.js.coffee)7
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js.coffee (renamed from app/assets/javascripts/lib/datetime_utility.js.coffee)4
-rw-r--r--app/assets/javascripts/lib/utils/emoji_aliases.js.coffee.erb (renamed from app/assets/javascripts/lib/emoji_aliases.js.coffee.erb)0
-rw-r--r--app/assets/javascripts/lib/utils/jquery.timeago.js (renamed from app/assets/javascripts/lib/jquery.timeago.js)0
-rw-r--r--app/assets/javascripts/lib/utils/md5.js (renamed from app/assets/javascripts/lib/md5.js)0
-rw-r--r--app/assets/javascripts/lib/utils/notify.js.coffee (renamed from app/assets/javascripts/lib/notify.js.coffee)0
-rw-r--r--app/assets/javascripts/lib/utils/text_utility.js.coffee105
-rw-r--r--app/assets/javascripts/lib/utils/type_utility.js.coffee (renamed from app/assets/javascripts/lib/type_utility.js.coffee)0
-rw-r--r--app/assets/javascripts/lib/utils/url_utility.js.coffee (renamed from app/assets/javascripts/lib/url_utility.js.coffee)0
-rw-r--r--app/assets/javascripts/lib/utils/utf8_encode.js (renamed from app/assets/javascripts/lib/utf8_encode.js)0
-rw-r--r--app/assets/javascripts/merge_request_tabs.js.coffee3
-rw-r--r--app/assets/javascripts/milestone.js.coffee63
-rw-r--r--app/assets/javascripts/milestone_select.js.coffee6
-rw-r--r--app/assets/javascripts/namespace_select.js.coffee79
-rw-r--r--app/assets/javascripts/network/application.js.coffee3
-rw-r--r--app/assets/javascripts/notes.js.coffee72
-rw-r--r--app/assets/javascripts/notifications_dropdown.js.coffee7
-rw-r--r--app/assets/javascripts/profile/application.js.coffee2
-rw-r--r--app/assets/javascripts/profile/gl_crop.js.coffee (renamed from app/assets/javascripts/gl_crop.js.coffee)0
-rw-r--r--app/assets/javascripts/profile/profile.js.coffee (renamed from app/assets/javascripts/profile.js.coffee)3
-rw-r--r--app/assets/javascripts/project.js.coffee41
-rw-r--r--app/assets/javascripts/projects_list.js.coffee11
-rw-r--r--app/assets/javascripts/protected_branch_select.js.coffee40
-rw-r--r--app/assets/javascripts/protected_branches.js.coffee9
-rw-r--r--app/assets/javascripts/search_autocomplete.js.coffee11
-rw-r--r--app/assets/javascripts/shortcuts.js.coffee19
-rw-r--r--app/assets/javascripts/single_file_diff.js.coffee54
-rw-r--r--app/assets/javascripts/subscription_select.js.coffee18
-rw-r--r--app/assets/javascripts/tree.js.coffee12
-rw-r--r--app/assets/javascripts/u2f/authenticate.js.coffee18
-rw-r--r--app/assets/javascripts/u2f/util.js.coffee3
-rw-r--r--app/assets/javascripts/u2f/util.js.coffee.erb15
-rw-r--r--app/assets/javascripts/users/application.js.coffee6
-rw-r--r--app/assets/javascripts/users/calendar.js.coffee5
-rw-r--r--app/assets/javascripts/users_select.js.coffee18
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/avatar.scss1
-rw-r--r--app/assets/stylesheets/framework/blank.scss51
-rw-r--r--app/assets/stylesheets/framework/blocks.scss27
-rw-r--r--app/assets/stylesheets/framework/buttons.scss36
-rw-r--r--app/assets/stylesheets/framework/common.scss15
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss31
-rw-r--r--app/assets/stylesheets/framework/files.scss4
-rw-r--r--app/assets/stylesheets/framework/flash.scss27
-rw-r--r--app/assets/stylesheets/framework/gitlab-theme.scss12
-rw-r--r--app/assets/stylesheets/framework/header.scss26
-rw-r--r--app/assets/stylesheets/framework/lists.scss15
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss31
-rw-r--r--app/assets/stylesheets/framework/mixins.scss14
-rw-r--r--app/assets/stylesheets/framework/mobile.scss4
-rw-r--r--app/assets/stylesheets/framework/nav.scss121
-rw-r--r--app/assets/stylesheets/framework/panels.scss4
-rw-r--r--app/assets/stylesheets/framework/selects.scss5
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss90
-rw-r--r--app/assets/stylesheets/framework/typography.scss32
-rw-r--r--app/assets/stylesheets/framework/variables.scss16
-rw-r--r--app/assets/stylesheets/pages/admin.scss27
-rw-r--r--app/assets/stylesheets/pages/awards.scss21
-rw-r--r--app/assets/stylesheets/pages/builds.scss8
-rw-r--r--app/assets/stylesheets/pages/commits.scss4
-rw-r--r--app/assets/stylesheets/pages/diff.scss15
-rw-r--r--app/assets/stylesheets/pages/editor.scss11
-rw-r--r--app/assets/stylesheets/pages/emojis.scss2607
-rw-r--r--app/assets/stylesheets/pages/events.scss4
-rw-r--r--app/assets/stylesheets/pages/groups.scss19
-rw-r--r--app/assets/stylesheets/pages/help.scss8
-rw-r--r--app/assets/stylesheets/pages/issuable.scss11
-rw-r--r--app/assets/stylesheets/pages/issues.scss12
-rw-r--r--app/assets/stylesheets/pages/labels.scss13
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss48
-rw-r--r--app/assets/stylesheets/pages/note_form.scss17
-rw-r--r--app/assets/stylesheets/pages/notes.scss28
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss199
-rw-r--r--app/assets/stylesheets/pages/projects.scss489
-rw-r--r--app/assets/stylesheets/pages/search.scss4
-rw-r--r--app/assets/stylesheets/pages/stat_graph.scss22
-rw-r--r--app/assets/stylesheets/pages/status.scss20
-rw-r--r--app/assets/stylesheets/pages/tags.scss7
-rw-r--r--app/assets/stylesheets/pages/todos.scss4
-rw-r--r--app/assets/stylesheets/pages/tree.scss8
-rw-r--r--app/controllers/admin/appearances_controller.rb1
-rw-r--r--app/controllers/admin/application_settings_controller.rb3
-rw-r--r--app/controllers/admin/builds_controller.rb4
-rw-r--r--app/controllers/admin/groups_controller.rb1
-rw-r--r--app/controllers/admin/hooks_controller.rb1
-rw-r--r--app/controllers/admin/projects_controller.rb8
-rw-r--r--app/controllers/admin/runner_projects_controller.rb9
-rw-r--r--app/controllers/admin/system_info_controller.rb59
-rw-r--r--app/controllers/application_controller.rb8
-rw-r--r--app/controllers/ci/projects_controller.rb2
-rw-r--r--app/controllers/concerns/authenticates_with_two_factor.rb10
-rw-r--r--app/controllers/concerns/creates_commit.rb3
-rw-r--r--app/controllers/concerns/diff_for_path.rb24
-rw-r--r--app/controllers/concerns/membership_actions.rb36
-rw-r--r--app/controllers/confirmations_controller.rb1
-rw-r--r--app/controllers/dashboard/groups_controller.rb2
-rw-r--r--app/controllers/dashboard/todos_controller.rb13
-rw-r--r--app/controllers/groups/group_members_controller.rb14
-rw-r--r--app/controllers/groups_controller.rb23
-rw-r--r--app/controllers/help_controller.rb10
-rw-r--r--app/controllers/import/base_controller.rb1
-rw-r--r--app/controllers/import/fogbugz_controller.rb2
-rw-r--r--app/controllers/import/github_controller.rb25
-rw-r--r--app/controllers/import/gitlab_projects_controller.rb11
-rw-r--r--app/controllers/import/gitorious_controller.rb1
-rw-r--r--app/controllers/import/google_code_controller.rb2
-rw-r--r--app/controllers/invites_controller.rb1
-rw-r--r--app/controllers/notification_settings_controller.rb22
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb6
-rw-r--r--app/controllers/profiles/two_factor_auths_controller.rb3
-rw-r--r--app/controllers/projects/application_controller.rb2
-rw-r--r--app/controllers/projects/artifacts_controller.rb5
-rw-r--r--app/controllers/projects/blob_controller.rb15
-rw-r--r--app/controllers/projects/builds_controller.rb17
-rw-r--r--app/controllers/projects/commit_controller.rb43
-rw-r--r--app/controllers/projects/compare_controller.rb48
-rw-r--r--app/controllers/projects/git_http_controller.rb63
-rw-r--r--app/controllers/projects/issues_controller.rb9
-rw-r--r--app/controllers/projects/merge_requests_controller.rb177
-rw-r--r--app/controllers/projects/network_controller.rb1
-rw-r--r--app/controllers/projects/notes_controller.rb31
-rw-r--r--app/controllers/projects/pipelines_controller.rb2
-rw-r--r--app/controllers/projects/project_members_controller.rb17
-rw-r--r--app/controllers/projects/protected_branches_controller.rb28
-rw-r--r--app/controllers/projects/runner_projects_controller.rb5
-rw-r--r--app/controllers/projects/runners_controller.rb7
-rw-r--r--app/controllers/projects/services_controller.rb3
-rw-r--r--app/controllers/projects/snippets_controller.rb2
-rw-r--r--app/controllers/projects/todos_controller.rb2
-rw-r--r--app/controllers/projects/wikis_controller.rb1
-rw-r--r--app/controllers/projects_controller.rb39
-rw-r--r--app/finders/pipelines_finder.rb4
-rw-r--r--app/finders/todos_finder.rb25
-rw-r--r--app/helpers/appearances_helper.rb4
-rw-r--r--app/helpers/application_helper.rb31
-rw-r--r--app/helpers/application_settings_helper.rb30
-rw-r--r--app/helpers/blob_helper.rb27
-rw-r--r--app/helpers/branches_helper.rb4
-rw-r--r--app/helpers/button_helper.rb38
-rw-r--r--app/helpers/ci_status_helper.rb16
-rw-r--r--app/helpers/diff_helper.rb34
-rw-r--r--app/helpers/dropdowns_helper.rb4
-rw-r--r--app/helpers/emails_helper.rb1
-rw-r--r--app/helpers/gitlab_markdown_helper.rb15
-rw-r--r--app/helpers/issuables_helper.rb3
-rw-r--r--app/helpers/issues_helper.rb2
-rw-r--r--app/helpers/javascript_helper.rb6
-rw-r--r--app/helpers/kerberos_spnego_helper.rb9
-rw-r--r--app/helpers/labels_helper.rb12
-rw-r--r--app/helpers/merge_requests_helper.rb10
-rw-r--r--app/helpers/notes_helper.rb100
-rw-r--r--app/helpers/notifications_helper.rb2
-rw-r--r--app/helpers/page_layout_helper.rb2
-rw-r--r--app/helpers/projects_helper.rb31
-rw-r--r--app/helpers/search_helper.rb19
-rw-r--r--app/helpers/time_helper.rb27
-rw-r--r--app/helpers/todos_helper.rb5
-rw-r--r--app/helpers/u2f_helper.rb5
-rw-r--r--app/helpers/workhorse_helper.rb15
-rw-r--r--app/mailers/emails/builds.rb1
-rw-r--r--app/mailers/emails/members.rb5
-rw-r--r--app/models/ability.rb48
-rw-r--r--app/models/application_setting.rb9
-rw-r--r--app/models/award_emoji.rb2
-rw-r--r--app/models/ci/build.rb97
-rw-r--r--app/models/ci/pipeline.rb41
-rw-r--r--app/models/ci/runner.rb23
-rw-r--r--app/models/ci/trigger_request.rb2
-rw-r--r--app/models/ci/variable.rb1
-rw-r--r--app/models/commit.rb33
-rw-r--r--app/models/commit_range.rb4
-rw-r--r--app/models/commit_status.rb6
-rw-r--r--app/models/concerns/awardable.rb21
-rw-r--r--app/models/concerns/issuable.rb40
-rw-r--r--app/models/concerns/mentionable.rb10
-rw-r--r--app/models/concerns/note_on_diff.rb52
-rw-r--r--app/models/concerns/participable.rb17
-rw-r--r--app/models/concerns/statuseable.rb6
-rw-r--r--app/models/deployment.rb10
-rw-r--r--app/models/diff_note.rb127
-rw-r--r--app/models/event.rb6
-rw-r--r--app/models/group.rb9
-rw-r--r--app/models/issue.rb2
-rw-r--r--app/models/key.rb2
-rw-r--r--app/models/label.rb19
-rw-r--r--app/models/legacy_diff_note.rb73
-rw-r--r--app/models/member.rb10
-rw-r--r--app/models/members/group_member.rb12
-rw-r--r--app/models/members/project_member.rb13
-rw-r--r--app/models/merge_request.rb236
-rw-r--r--app/models/merge_request_diff.rb184
-rw-r--r--app/models/milestone.rb1
-rw-r--r--app/models/namespace.rb95
-rw-r--r--app/models/network/graph.rb4
-rw-r--r--app/models/note.rb43
-rw-r--r--app/models/notification_setting.rb9
-rw-r--r--app/models/project.rb90
-rw-r--r--app/models/project_import_data.rb1
-rw-r--r--app/models/project_services/bugzilla_service.rb23
-rw-r--r--app/models/project_services/builds_email_service.rb27
-rw-r--r--app/models/project_services/custom_issue_tracker_service.rb5
-rw-r--r--app/models/project_services/drone_ci_service.rb1
-rw-r--r--app/models/project_services/hipchat_service.rb2
-rw-r--r--app/models/project_services/irker_service.rb10
-rw-r--r--app/models/project_services/issue_tracker_service.rb1
-rw-r--r--app/models/project_services/jira_service.rb5
-rw-r--r--app/models/project_services/redmine_service.rb1
-rw-r--r--app/models/project_team.rb24
-rw-r--r--app/models/project_wiki.rb4
-rw-r--r--app/models/protected_branch.rb47
-rw-r--r--app/models/repository.rb185
-rw-r--r--app/models/sent_notification.rb66
-rw-r--r--app/models/service.rb10
-rw-r--r--app/models/snippet.rb17
-rw-r--r--app/models/todo.rb29
-rw-r--r--app/models/user.rb22
-rw-r--r--app/services/audit_event_service.rb2
-rw-r--r--app/services/auth/container_registry_authentication_service.rb2
-rw-r--r--app/services/ci/create_builds_service.rb10
-rw-r--r--app/services/ci/create_pipeline_service.rb1
-rw-r--r--app/services/ci/register_build_service.rb2
-rw-r--r--app/services/commits/change_service.rb4
-rw-r--r--app/services/compare_service.rb2
-rw-r--r--app/services/create_branch_service.rb40
-rw-r--r--app/services/create_commit_builds_service.rb8
-rw-r--r--app/services/create_release_service.rb5
-rw-r--r--app/services/create_snippet_service.rb10
-rw-r--r--app/services/create_tag_service.rb6
-rw-r--r--app/services/delete_branch_service.rb16
-rw-r--r--app/services/delete_tag_service.rb9
-rw-r--r--app/services/files/base_service.rb9
-rw-r--r--app/services/files/create_service.rb2
-rw-r--r--app/services/files/update_service.rb5
-rw-r--r--app/services/git_hooks_service.rb6
-rw-r--r--app/services/git_push_service.rb3
-rw-r--r--app/services/git_tag_push_service.rb1
-rw-r--r--app/services/issuable_base_service.rb10
-rw-r--r--app/services/issues/base_service.rb1
-rw-r--r--app/services/issues/bulk_update_service.rb3
-rw-r--r--app/services/members/destroy_service.rb21
-rw-r--r--app/services/merge_requests/base_service.rb1
-rw-r--r--app/services/merge_requests/build_service.rb2
-rw-r--r--app/services/merge_requests/merge_service.rb9
-rw-r--r--app/services/merge_requests/merge_when_build_succeeds_service.rb3
-rw-r--r--app/services/merge_requests/post_merge_service.rb1
-rw-r--r--app/services/merge_requests/refresh_service.rb23
-rw-r--r--app/services/merge_requests/reopen_service.rb2
-rw-r--r--app/services/milestones/destroy_service.rb1
-rw-r--r--app/services/notes/create_service.rb1
-rw-r--r--app/services/notes/diff_position_update_service.rb30
-rw-r--r--app/services/notification_service.rb23
-rw-r--r--app/services/projects/destroy_service.rb6
-rw-r--r--app/services/projects/download_service.rb1
-rw-r--r--app/services/projects/housekeeping_service.rb26
-rw-r--r--app/services/projects/import_export/export_service.rb13
-rw-r--r--app/services/projects/import_service.rb4
-rw-r--r--app/services/projects/transfer_service.rb4
-rw-r--r--app/services/projects/update_service.rb3
-rw-r--r--app/services/system_note_service.rb8
-rw-r--r--app/services/todo_service.rb8
-rw-r--r--app/services/update_release_service.rb5
-rw-r--r--app/services/update_snippet_service.rb1
-rw-r--r--app/services/wiki_pages/base_service.rb1
-rw-r--r--app/uploaders/avatar_uploader.rb4
-rw-r--r--app/uploaders/lfs_object_uploader.rb2
-rw-r--r--app/validators/addressable_url_validator.rb45
-rw-r--r--app/views/admin/abuse_reports/_abuse_report.html.haml4
-rw-r--r--app/views/admin/appearances/_form.html.haml4
-rw-r--r--app/views/admin/appearances/preview.html.haml34
-rw-r--r--app/views/admin/appearances/show.html.haml2
-rw-r--r--app/views/admin/application_settings/_form.html.haml30
-rw-r--r--app/views/admin/application_settings/show.html.haml1
-rw-r--r--app/views/admin/background_jobs/_head.html.haml4
-rw-r--r--app/views/admin/background_jobs/show.html.haml2
-rw-r--r--app/views/admin/builds/_build.html.haml65
-rw-r--r--app/views/admin/builds/index.html.haml21
-rw-r--r--app/views/admin/dashboard/index.html.haml2
-rw-r--r--app/views/admin/deploy_keys/new.html.haml2
-rw-r--r--app/views/admin/groups/_group.html.haml12
-rw-r--r--app/views/admin/groups/index.html.haml62
-rw-r--r--app/views/admin/groups/show.html.haml33
-rw-r--r--app/views/admin/health_check/show.html.haml2
-rw-r--r--app/views/admin/hooks/index.html.haml2
-rw-r--r--app/views/admin/logs/show.html.haml2
-rw-r--r--app/views/admin/projects/index.html.haml173
-rw-r--r--app/views/admin/projects/show.html.haml55
-rw-r--r--app/views/admin/runners/_runner.html.haml2
-rw-r--r--app/views/admin/runners/index.html.haml5
-rw-r--r--app/views/admin/runners/show.html.haml4
-rw-r--r--app/views/admin/system_info/show.html.haml25
-rw-r--r--app/views/admin/users/_form.html.haml2
-rw-r--r--app/views/admin/users/_user.html.haml42
-rw-r--r--app/views/admin/users/groups.html.haml5
-rw-r--r--app/views/admin/users/index.html.haml172
-rw-r--r--app/views/ci/errors/show.haml2
-rw-r--r--app/views/ci/shared/_guide.html.haml13
-rw-r--r--app/views/ci/shared/_no_runners.html.haml7
-rw-r--r--app/views/dashboard/_projects_head.html.haml2
-rw-r--r--app/views/dashboard/projects/_zero_authorized_projects.html.haml87
-rw-r--r--app/views/dashboard/projects/index.html.haml3
-rw-r--r--app/views/dashboard/todos/index.html.haml4
-rw-r--r--app/views/devise/sessions/two_factor.html.haml4
-rw-r--r--app/views/emojis/index.html.haml2
-rw-r--r--app/views/errors/access_denied.html.haml2
-rw-r--r--app/views/events/event/_push.html.haml25
-rw-r--r--app/views/explore/snippets/index.html.haml1
-rw-r--r--app/views/groups/group_members/_new_group_member.html.haml2
-rw-r--r--app/views/groups/group_members/index.html.haml11
-rw-r--r--app/views/groups/milestones/index.html.haml1
-rw-r--r--app/views/groups/projects.html.haml1
-rw-r--r--app/views/groups/show.html.haml15
-rw-r--r--app/views/help/_shortcuts.html.haml10
-rw-r--r--app/views/help/index.html.haml2
-rw-r--r--app/views/help/show.html.haml2
-rw-r--r--app/views/help/ui.html.haml2
-rw-r--r--app/views/import/base/create.js.haml5
-rw-r--r--app/views/import/github/new.html.haml43
-rw-r--r--app/views/layouts/_collapse_button.html.haml3
-rw-r--r--app/views/layouts/_flash.html.haml2
-rw-r--r--app/views/layouts/_head.html.haml4
-rw-r--r--app/views/layouts/_init_auto_complete.html.haml1
-rw-r--r--app/views/layouts/_page.html.haml17
-rw-r--r--app/views/layouts/_search.html.haml2
-rw-r--r--app/views/layouts/ci/_info.html.haml2
-rw-r--r--app/views/layouts/ci/_page.html.haml22
-rw-r--r--app/views/layouts/ci/notify.html.haml19
-rw-r--r--app/views/layouts/header/_default.html.haml30
-rw-r--r--app/views/layouts/nav/_admin.html.haml90
-rw-r--r--app/views/layouts/nav/_admin_settings.html.haml31
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml20
-rw-r--r--app/views/layouts/nav/_group.html.haml9
-rw-r--r--app/views/layouts/nav/_group_settings.html.haml36
-rw-r--r--app/views/layouts/nav/_profile.html.haml91
-rw-r--r--app/views/layouts/nav/_project.html.haml23
-rw-r--r--app/views/layouts/nav/_project_settings.html.haml2
-rw-r--r--app/views/notify/note_merge_request_email.html.haml4
-rw-r--r--app/views/notify/project_was_not_exported_email.html.haml2
-rw-r--r--app/views/notify/project_was_not_exported_email.text.erb6
-rw-r--r--app/views/notify/project_was_not_exported_email.text.haml6
-rw-r--r--app/views/notify/repository_push_email.html.haml5
-rw-r--r--app/views/profiles/_head.html.haml3
-rw-r--r--app/views/profiles/accounts/show.html.haml1
-rw-r--r--app/views/profiles/audit_log.html.haml1
-rw-r--r--app/views/profiles/emails/index.html.haml1
-rw-r--r--app/views/profiles/keys/_form.html.haml2
-rw-r--r--app/views/profiles/keys/index.html.haml2
-rw-r--r--app/views/profiles/keys/show.html.haml1
-rw-r--r--app/views/profiles/notifications/show.html.haml3
-rw-r--r--app/views/profiles/personal_access_tokens/index.html.haml1
-rw-r--r--app/views/profiles/preferences/show.html.haml5
-rw-r--r--app/views/profiles/show.html.haml2
-rw-r--r--app/views/profiles/two_factor_auths/show.html.haml7
-rw-r--r--app/views/projects/_bitbucket_import_modal.html.haml2
-rw-r--r--app/views/projects/_builds_settings.html.haml2
-rw-r--r--app/views/projects/_github_import_modal.html.haml13
-rw-r--r--app/views/projects/_gitlab_import_modal.html.haml2
-rw-r--r--app/views/projects/_home_panel.html.haml54
-rw-r--r--app/views/projects/_last_commit.html.haml19
-rw-r--r--app/views/projects/_last_push.html.haml6
-rw-r--r--app/views/projects/_md_preview.html.haml17
-rw-r--r--app/views/projects/_merge_request_settings.html.haml2
-rw-r--r--app/views/projects/badges/index.html.haml2
-rw-r--r--app/views/projects/blob/_editor.html.haml6
-rw-r--r--app/views/projects/blob/_text.html.haml2
-rw-r--r--app/views/projects/blob/edit.html.haml2
-rw-r--r--app/views/projects/blob/show.html.haml17
-rw-r--r--app/views/projects/branches/index.html.haml4
-rw-r--r--app/views/projects/builds/_sidebar.html.haml6
-rw-r--r--app/views/projects/builds/index.html.haml23
-rw-r--r--app/views/projects/builds/show.html.haml2
-rw-r--r--app/views/projects/buttons/_fork.html.haml9
-rw-r--r--app/views/projects/ci/builds/_build.html.haml89
-rw-r--r--app/views/projects/ci/pipelines/_pipeline.html.haml89
-rw-r--r--app/views/projects/commit/_ci_stage.html.haml2
-rw-r--r--app/views/projects/commit/_pipeline.html.haml4
-rw-r--r--app/views/projects/commit/show.html.haml3
-rw-r--r--app/views/projects/commits/_commit.html.haml47
-rw-r--r--app/views/projects/commits/_head.html.haml8
-rw-r--r--app/views/projects/commits/show.html.haml2
-rw-r--r--app/views/projects/compare/_form.html.haml18
-rw-r--r--app/views/projects/compare/_ref_dropdown.html.haml4
-rw-r--r--app/views/projects/compare/index.html.haml4
-rw-r--r--app/views/projects/compare/show.html.haml36
-rw-r--r--app/views/projects/container_registry/_tag.html.haml6
-rw-r--r--app/views/projects/deploy_keys/_form.html.haml2
-rw-r--r--app/views/projects/deployments/_actions.haml22
-rw-r--r--app/views/projects/deployments/_deployment.html.haml10
-rw-r--r--app/views/projects/diffs/_content.html.haml29
-rw-r--r--app/views/projects/diffs/_diffs.html.haml8
-rw-r--r--app/views/projects/diffs/_file.html.haml47
-rw-r--r--app/views/projects/diffs/_file_header.html.haml25
-rw-r--r--app/views/projects/diffs/_image.html.haml11
-rw-r--r--app/views/projects/diffs/_line.html.haml21
-rw-r--r--app/views/projects/diffs/_match_line_parallel.html.haml4
-rw-r--r--app/views/projects/diffs/_parallel_view.html.haml35
-rw-r--r--app/views/projects/diffs/_text_file.html.haml15
-rw-r--r--app/views/projects/diffs/_warning.html.haml3
-rw-r--r--app/views/projects/edit.html.haml2
-rw-r--r--app/views/projects/environments/_environment.html.haml3
-rw-r--r--app/views/projects/environments/index.html.haml19
-rw-r--r--app/views/projects/environments/new.html.haml5
-rw-r--r--app/views/projects/environments/show.html.haml16
-rw-r--r--app/views/projects/forks/index.html.haml10
-rw-r--r--app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml4
-rw-r--r--app/views/projects/graphs/_head.html.haml28
-rw-r--r--app/views/projects/graphs/ci.html.haml25
-rw-r--r--app/views/projects/graphs/commits.html.haml90
-rw-r--r--app/views/projects/graphs/languages.html.haml36
-rw-r--r--app/views/projects/graphs/show.html.haml48
-rw-r--r--app/views/projects/imports/new.html.haml2
-rw-r--r--app/views/projects/issues/index.html.haml46
-rw-r--r--app/views/projects/labels/index.html.haml2
-rw-r--r--app/views/projects/merge_requests/_discussion.html.haml4
-rw-r--r--app/views/projects/merge_requests/_show.html.haml4
-rw-r--r--app/views/projects/merge_requests/index.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_how_to_merge.html.haml6
-rw-r--r--app/views/projects/merge_requests/show/_mr_title.html.haml8
-rw-r--r--app/views/projects/merge_requests/widget/_heading.html.haml10
-rw-r--r--app/views/projects/merge_requests/widget/_open.html.haml6
-rw-r--r--app/views/projects/merge_requests/widget/open/_accept.html.haml2
-rw-r--r--app/views/projects/merge_requests/widget/open/_conflicts.html.haml2
-rw-r--r--app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml2
-rw-r--r--app/views/projects/milestones/index.html.haml2
-rw-r--r--app/views/projects/network/_head.html.haml2
-rw-r--r--app/views/projects/network/show.html.haml8
-rw-r--r--app/views/projects/new.html.haml208
-rw-r--r--app/views/projects/notes/_diff_notes_with_reply.html.haml3
-rw-r--r--app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml6
-rw-r--r--app/views/projects/notes/_form.html.haml1
-rw-r--r--app/views/projects/notes/_hints.html.haml4
-rw-r--r--app/views/projects/notes/_note.html.haml8
-rw-r--r--app/views/projects/notes/_notes_with_form.html.haml6
-rw-r--r--app/views/projects/notes/discussions/_diff_with_notes.html.haml35
-rw-r--r--app/views/projects/notes/discussions/_notes.html.haml3
-rw-r--r--app/views/projects/pipelines/index.html.haml12
-rw-r--r--app/views/projects/project_members/_group_members.html.haml3
-rw-r--r--app/views/projects/project_members/_new_project_member.html.haml2
-rw-r--r--app/views/projects/project_members/_shared_group_members.html.haml5
-rw-r--r--app/views/projects/project_members/_team.html.haml3
-rw-r--r--app/views/projects/project_members/index.html.haml4
-rw-r--r--app/views/projects/protected_branches/_branches_list.html.haml35
-rw-r--r--app/views/projects/protected_branches/_dropdown.html.haml17
-rw-r--r--app/views/projects/protected_branches/_matching_branch.html.haml9
-rw-r--r--app/views/projects/protected_branches/_protected_branch.html.haml23
-rw-r--r--app/views/projects/protected_branches/index.html.haml36
-rw-r--r--app/views/projects/protected_branches/show.html.haml25
-rw-r--r--app/views/projects/runners/_form.html.haml6
-rw-r--r--app/views/projects/runners/_runner.html.haml6
-rw-r--r--app/views/projects/runners/_specific_runners.html.haml10
-rw-r--r--app/views/projects/runners/show.html.haml3
-rw-r--r--app/views/projects/services/_form.html.haml2
-rw-r--r--app/views/projects/show.html.haml97
-rw-r--r--app/views/projects/snippets/_actions.html.haml42
-rw-r--r--app/views/projects/snippets/index.html.haml8
-rw-r--r--app/views/projects/tags/index.html.haml32
-rw-r--r--app/views/projects/tags/show.html.haml67
-rw-r--r--app/views/projects/tree/show.html.haml2
-rw-r--r--app/views/projects/wikis/_main_links.html.haml3
-rw-r--r--app/views/projects/wikis/_nav.html.haml11
-rw-r--r--app/views/projects/wikis/_new.html.haml31
-rw-r--r--app/views/projects/wikis/edit.html.haml32
-rw-r--r--app/views/projects/wikis/git_access.html.haml52
-rw-r--r--app/views/projects/wikis/history.html.haml66
-rw-r--r--app/views/projects/wikis/pages.html.haml18
-rw-r--r--app/views/projects/wikis/show.html.haml34
-rw-r--r--app/views/search/results/_blob.html.haml5
-rw-r--r--app/views/shared/_clone_panel.html.haml23
-rw-r--r--app/views/shared/_event_filter.html.haml2
-rw-r--r--app/views/shared/_file_highlight.html.haml7
-rw-r--r--app/views/shared/_import_form.html.haml2
-rw-r--r--app/views/shared/_labels_row.html.haml7
-rw-r--r--app/views/shared/_ref_switcher.html.haml9
-rw-r--r--app/views/shared/_visibility_level.html.haml2
-rw-r--r--app/views/shared/icons/_group.svg.erb (renamed from app/views/shared/icons/_group.svg)9
-rw-r--r--app/views/shared/icons/_icon_commit.svg3
-rw-r--r--app/views/shared/icons/_icon_fork.svg1
-rw-r--r--app/views/shared/icons/_icon_status_cancel.svg12
-rw-r--r--app/views/shared/icons/_icon_status_failed.svg12
-rw-r--r--app/views/shared/icons/_icon_status_pending.svg13
-rw-r--r--app/views/shared/icons/_icon_status_running.svg12
-rw-r--r--app/views/shared/icons/_icon_status_success.svg15
-rw-r--r--app/views/shared/icons/_icon_status_warning.svg15
-rw-r--r--app/views/shared/icons/_icon_timer.svg1
-rw-r--r--app/views/shared/icons/_issues.svg13
-rw-r--r--app/views/shared/icons/_issues.svg.erb4
-rw-r--r--app/views/shared/icons/_project.svg10
-rw-r--r--app/views/shared/icons/_project.svg.erb3
-rw-r--r--app/views/shared/issuable/_filter.html.haml9
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml2
-rw-r--r--app/views/shared/members/_access_request_buttons.html.haml18
-rw-r--r--app/views/shared/members/_member.html.haml85
-rw-r--r--app/views/shared/members/_requests.html.haml6
-rw-r--r--app/views/shared/milestones/_issuable.html.haml9
-rw-r--r--app/views/shared/milestones/_summary.html.haml1
-rw-r--r--app/views/shared/projects/_dropdown.html.haml17
-rw-r--r--app/views/shared/projects/_list.html.haml6
-rw-r--r--app/views/shared/web_hooks/_form.html.haml2
-rw-r--r--app/views/snippets/_actions.html.haml41
-rw-r--r--app/views/users/calendar_activities.html.haml2
-rw-r--r--app/views/users/show.html.haml9
-rw-r--r--app/workers/emails_on_push_worker.rb16
-rw-r--r--app/workers/git_garbage_collect_worker.rb16
-rw-r--r--app/workers/gitlab_shell_one_shot_worker.rb10
-rw-r--r--app/workers/post_receive.rb6
-rw-r--r--app/workers/project_export_worker.rb2
-rw-r--r--app/workers/repository_fork_worker.rb2
-rw-r--r--config/application.rb6
-rw-r--r--config/dependency_decisions.yml6
-rw-r--r--config/environments/development.rb3
-rw-r--r--config/gitlab.teatro.yml85
-rw-r--r--config/gitlab.yml.example14
-rw-r--r--config/initializers/1_settings.rb15
-rw-r--r--config/initializers/6_validations.rb24
-rw-r--r--config/initializers/devise.rb2
-rw-r--r--config/initializers/gitlab_shell_secret_token.rb20
-rw-r--r--config/initializers/haml.rb7
-rw-r--r--config/initializers/hamlit.rb18
-rw-r--r--config/initializers/health_check.rb1
-rw-r--r--config/initializers/metrics.rb9
-rw-r--r--config/initializers/rack_attack.rb.example3
-rw-r--r--config/initializers/relative_naming_ci_namespace.rb16
-rw-r--r--config/initializers/secure_headers.rb109
-rw-r--r--config/initializers/sidekiq.rb13
-rw-r--r--config/initializers/smtp_settings.rb.sample1
-rw-r--r--config/initializers/trusted_proxies.rb13
-rw-r--r--config/routes.rb35
-rw-r--r--db/fixtures/development/14_builds.rb63
-rw-r--r--db/migrate/20160508202603_add_head_commit_id_to_merge_request_diffs.rb5
-rw-r--r--db/migrate/20160508215920_add_positions_to_diff_notes.rb6
-rw-r--r--db/migrate/20160509091049_add_locked_to_ci_runner.rb13
-rw-r--r--db/migrate/20160516224534_add_start_commit_id_to_merge_request_diffs.rb5
-rw-r--r--db/migrate/20160519203051_add_developers_can_merge_to_protected_branches.rb9
-rw-r--r--db/migrate/20160522215720_add_note_type_and_position_to_sent_notification.rb22
-rw-r--r--db/migrate/20160608195742_add_repository_storage_to_projects.rb12
-rw-r--r--db/migrate/20160608211215_add_user_default_external_to_application_settings.rb13
-rw-r--r--db/migrate/20160614182521_add_repository_storage_to_application_settings.rb5
-rw-r--r--db/migrate/20160615173316_add_enabled_git_access_protocols_to_application_settings.rb11
-rw-r--r--db/migrate/20160615191922_set_missing_stage_on_ci_builds.rb9
-rw-r--r--db/migrate/20160616102642_remove_duplicated_keys.rb19
-rw-r--r--db/migrate/20160616103005_remove_keys_fingerprint_index_if_exists.rb21
-rw-r--r--db/migrate/20160616103948_add_unique_index_to_keys_fingerprint.rb13
-rw-r--r--db/migrate/20160620115026_add_index_on_runners_locked.rb12
-rw-r--r--db/migrate/20160628085157_add_artifacts_size_to_ci_builds.rb7
-rw-r--r--db/migrate/20160629025435_add_column_in_progress_merge_commit_sha_to_merge_requests.rb8
-rw-r--r--db/migrate/20160703180340_add_index_on_award_emoji_user_and_name.rb11
-rw-r--r--db/migrate/20160705163108_remove_requesters_that_are_owners.rb40
-rw-r--r--db/migrate/20160712171823_remove_award_emojis_with_no_user.rb21
-rw-r--r--db/migrate/20160715132507_add_user_id_to_pipeline.rb7
-rw-r--r--db/migrate/20160715134306_add_index_for_pipeline_user_id.rb9
-rw-r--r--db/migrate/20160716115710_add_when_and_yaml_variables_to_ci_builds.rb8
-rw-r--r--db/schema.rb59
-rw-r--r--doc/README.md30
-rw-r--r--doc/administration/access_restrictions.md38
-rw-r--r--doc/administration/auth/ldap.md42
-rw-r--r--doc/administration/container_registry.md86
-rw-r--r--doc/administration/custom_hooks.md57
-rw-r--r--doc/administration/img/access_restrictions.pngbin0 -> 317529 bytes
-rw-r--r--doc/administration/img/custom_hooks_error_msg.pngbin0 -> 159486 bytes
-rw-r--r--doc/administration/img/housekeeping_settings.pngbin23856 -> 19347 bytes
-rw-r--r--doc/administration/img/restricted_url.pngbin0 -> 188210 bytes
-rw-r--r--doc/administration/raketasks/project_import_export.md33
-rw-r--r--doc/administration/repository_storages.md18
-rw-r--r--doc/administration/troubleshooting/debug.md169
-rw-r--r--doc/administration/troubleshooting/gdb-stuck-ruby.txt142
-rw-r--r--doc/api/README.md2
-rw-r--r--doc/api/branches.md20
-rw-r--r--doc/api/builds.md5
-rw-r--r--doc/api/groups.md247
-rw-r--r--doc/api/issues.md200
-rw-r--r--doc/api/merge_requests.md146
-rw-r--r--doc/api/oauth2.md9
-rw-r--r--doc/api/projects.md119
-rw-r--r--doc/api/services.md36
-rw-r--r--doc/api/session.md9
-rw-r--r--doc/api/settings.md10
-rw-r--r--doc/api/todos.md296
-rw-r--r--doc/ci/README.md6
-rw-r--r--doc/ci/build_artifacts/img/build_artifacts_browser.pngbin89132 -> 82102 bytes
-rw-r--r--doc/ci/build_artifacts/img/build_artifacts_browser_button.pngbin11614 -> 7230 bytes
-rw-r--r--doc/ci/environments.md58
-rw-r--r--doc/ci/examples/README.md1
-rw-r--r--doc/ci/examples/php.md2
-rw-r--r--doc/ci/img/builds_tab.pngbin3845 -> 3047 bytes
-rw-r--r--doc/ci/img/features_settings.pngbin18691 -> 15809 bytes
-rw-r--r--doc/ci/permissions/README.md23
-rw-r--r--doc/ci/pipelines.md38
-rw-r--r--doc/ci/quick_start/README.md64
-rw-r--r--doc/ci/quick_start/img/build_log.pngbin63272 -> 52482 bytes
-rw-r--r--doc/ci/quick_start/img/builds_status.pngbin49121 -> 41838 bytes
-rw-r--r--doc/ci/quick_start/img/new_commit.pngbin9033 -> 7587 bytes
-rw-r--r--doc/ci/quick_start/img/pipelines_status.pngbin0 -> 89387 bytes
-rw-r--r--doc/ci/quick_start/img/runners_activated.pngbin27597 -> 22822 bytes
-rw-r--r--doc/ci/quick_start/img/single_commit_status_pending.pngbin36431 -> 29981 bytes
-rw-r--r--doc/ci/quick_start/img/status_pending.pngbin19782 -> 16205 bytes
-rw-r--r--doc/ci/runners/README.md8
-rw-r--r--doc/ci/runners/project_specific.pngbin31408 -> 30196 bytes
-rw-r--r--doc/ci/runners/shared_runner.pngbin18366 -> 17797 bytes
-rw-r--r--doc/ci/runners/shared_to_specific_admin.pngbin5897 -> 5715 bytes
-rw-r--r--doc/ci/triggers/img/builds_page.pngbin39713 -> 33324 bytes
-rw-r--r--doc/ci/triggers/img/trigger_single_build.pngbin2895 -> 2387 bytes
-rw-r--r--doc/ci/triggers/img/trigger_variables.pngbin5418 -> 4433 bytes
-rw-r--r--doc/ci/triggers/img/triggers_page.pngbin15889 -> 12943 bytes
-rw-r--r--doc/ci/yaml/README.md204
-rw-r--r--doc/container_registry/img/container_registry.pngbin354050 -> 222782 bytes
-rw-r--r--doc/container_registry/img/project_feature.pngbin392842 -> 248750 bytes
-rw-r--r--doc/customization/branded_login_page/appearance.pngbin365120 -> 156228 bytes
-rw-r--r--doc/customization/branded_login_page/custom_sign_in.pngbin314111 -> 166674 bytes
-rw-r--r--doc/customization/branded_login_page/default_login_page.pngbin292731 -> 150538 bytes
-rw-r--r--doc/customization/issue_closing.md2
-rw-r--r--doc/development/architecture.md4
-rw-r--r--doc/development/ci_setup.md2
-rw-r--r--doc/development/doc_styleguide.md58
-rw-r--r--doc/development/gitlab_architecture_diagram.pngbin0 -> 23831 bytes
-rw-r--r--doc/development/gitlab_diagram_overview.pngbin256612 -> 0 bytes
-rw-r--r--doc/development/gotchas.md2
-rw-r--r--doc/development/migration_style_guide.md1
-rw-r--r--doc/development/rake_tasks.md25
-rw-r--r--doc/development/ui_guide.md47
-rw-r--r--doc/downgrade_ee_to_ce/README.md4
-rw-r--r--doc/gitlab-basics/basicsimages/add_new_merge_request.pngbin9467 -> 9003 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/add_sshkey.pngbin1463 -> 1394 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/branch_info.pngbin7978 -> 7572 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/branch_name.pngbin2199 -> 2137 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/branches.pngbin3653 -> 3548 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/button-create-mr.pngbin6154 -> 5927 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/click-on-new-group.pngbin2063 -> 1957 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/commit_changes.pngbin5567 -> 4941 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/commit_message.pngbin5707 -> 5103 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/commits.pngbin4258 -> 4112 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/compare_branches.pngbin1624 -> 1520 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/create_file.pngbin2524 -> 2451 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/create_group.pngbin3224 -> 3184 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/edit_file.pngbin2259 -> 2221 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/file_located.pngbin3156 -> 3078 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/file_name.pngbin2544 -> 2412 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/find_file.pngbin8840 -> 8426 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/find_group.pngbin6159 -> 5897 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/fork.pngbin1046 -> 896 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/group_info.pngbin16217 -> 15479 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/groups.pngbin4857 -> 4752 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/https.pngbin2887 -> 2822 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/image_file.pngbin2939 -> 2796 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/issue_title.pngbin9059 -> 8311 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/issues.pngbin4332 -> 4153 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/key.pngbin1264 -> 1177 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/merge_requests.pngbin4381 -> 4213 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/new_merge_request.pngbin3227 -> 3162 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/new_project.pngbin2319 -> 2234 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/newbranch.pngbin1314 -> 1244 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/paste_sshkey.pngbin8620 -> 7699 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/profile_settings.pngbin1194 -> 1101 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/project_info.pngbin21862 -> 21041 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/public_file_link.pngbin3038 -> 3023 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/select-group.pngbin6075 -> 6034 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/select-group2.pngbin5049 -> 5040 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/select_branch.pngbin12213 -> 11207 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/select_project.pngbin16832 -> 16176 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/settings.pngbin4321 -> 4149 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/shh_keys.pngbin4981 -> 4782 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/submit_new_issue.pngbin9083 -> 8644 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/title_description_mr.pngbin12749 -> 11919 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/white_space.pngbin3707 -> 2192 bytes
-rw-r--r--doc/hooks/custom_hooks.md40
-rw-r--r--doc/install/database_mysql.md2
-rw-r--r--doc/install/installation.md10
-rw-r--r--doc/install/requirements.md2
-rw-r--r--doc/integration/external-issue-tracker.md3
-rw-r--r--doc/integration/github.md2
-rw-r--r--doc/integration/img/akismet_settings.pngbin55837 -> 26625 bytes
-rw-r--r--doc/integration/img/enabled-oauth-sign-in-sources.pngbin49081 -> 21767 bytes
-rw-r--r--doc/integration/img/facebook_api_keys.pngbin125921 -> 85832 bytes
-rw-r--r--doc/integration/img/facebook_app_settings.pngbin134387 -> 68086 bytes
-rw-r--r--doc/integration/img/facebook_website_url.pngbin42292 -> 19823 bytes
-rw-r--r--doc/integration/img/github_app.pngbin75297 -> 55591 bytes
-rw-r--r--doc/integration/img/gitlab_app.pngbin55325 -> 30963 bytes
-rw-r--r--doc/integration/img/gmail_action_buttons_for_gitlab.pngbin17321 -> 16020 bytes
-rw-r--r--doc/integration/img/google_app.pngbin52669 -> 29154 bytes
-rw-r--r--doc/integration/img/oauth_provider_admin_application.pngbin40579 -> 33440 bytes
-rw-r--r--doc/integration/img/oauth_provider_application_form.pngbin27974 -> 23048 bytes
-rw-r--r--doc/integration/img/oauth_provider_application_id_secret.pngbin33901 -> 27673 bytes
-rw-r--r--doc/integration/img/oauth_provider_authorized_application.pngbin32225 -> 26622 bytes
-rw-r--r--doc/integration/img/oauth_provider_user_wide_applications.pngbin40632 -> 33337 bytes
-rw-r--r--doc/integration/img/twitter_app_api_keys.pngbin72200 -> 36921 bytes
-rw-r--r--doc/integration/img/twitter_app_details.pngbin121621 -> 64686 bytes
-rw-r--r--doc/integration/oauth_provider.md3
-rw-r--r--doc/integration/omniauth.md12
-rw-r--r--doc/integration/saml.md4
-rw-r--r--doc/integration/shibboleth.md2
-rw-r--r--doc/intro/README.md2
-rw-r--r--doc/markdown/img/logo.pngbin11097 -> 9509 bytes
-rw-r--r--doc/markdown/markdown.md34
-rw-r--r--doc/monitoring/img/health_check_token.pngbin10884 -> 6630 bytes
-rw-r--r--doc/monitoring/performance/grafana_configuration.md74
-rw-r--r--doc/monitoring/performance/img/grafana_dashboard_dropdown.pngbin29419 -> 14368 bytes
-rw-r--r--doc/monitoring/performance/img/grafana_dashboard_import.pngbin40974 -> 18267 bytes
-rw-r--r--doc/monitoring/performance/img/grafana_data_source_configuration.pngbin53402 -> 26060 bytes
-rw-r--r--doc/monitoring/performance/img/grafana_data_source_empty.pngbin44058 -> 21821 bytes
-rw-r--r--doc/monitoring/performance/img/grafana_save_icon.pngbin16024 -> 9107 bytes
-rw-r--r--doc/monitoring/performance/img/metrics_gitlab_configuration_settings.pngbin45148 -> 37228 bytes
-rw-r--r--doc/permissions/permissions.md100
-rw-r--r--doc/profile/2fa.pngbin23415 -> 22047 bytes
-rw-r--r--doc/profile/2fa_auth.pngbin15569 -> 14535 bytes
-rw-r--r--doc/project_services/bugzilla.md17
-rw-r--r--doc/project_services/emails_on_push.md17
-rw-r--r--doc/project_services/img/builds_emails_service.pngbin41222 -> 33943 bytes
-rw-r--r--doc/project_services/img/emails_on_push_service.pngbin0 -> 98160 bytes
-rw-r--r--doc/project_services/img/jira_add_gitlab_commit_message.pngbin57136 -> 46590 bytes
-rw-r--r--doc/project_services/img/jira_add_user_to_group.pngbin59251 -> 41994 bytes
-rw-r--r--doc/project_services/img/jira_create_new_group.pngbin41294 -> 32934 bytes
-rw-r--r--doc/project_services/img/jira_create_new_group_name.pngbin12535 -> 9054 bytes
-rw-r--r--doc/project_services/img/jira_create_new_user.pngbin26532 -> 21081 bytes
-rw-r--r--doc/project_services/img/jira_group_access.pngbin46028 -> 32210 bytes
-rw-r--r--doc/project_services/img/jira_issue_closed.pngbin92601 -> 77028 bytes
-rw-r--r--doc/project_services/img/jira_issue_reference.pngbin39942 -> 36188 bytes
-rw-r--r--doc/project_services/img/jira_issues_workflow.pngbin105237 -> 87067 bytes
-rw-r--r--doc/project_services/img/jira_merge_request_close.pngbin111150 -> 102835 bytes
-rw-r--r--doc/project_services/img/jira_project_name.pngbin60598 -> 41572 bytes
-rw-r--r--doc/project_services/img/jira_reference_commit_message_in_jira_issue.pngbin42452 -> 33706 bytes
-rw-r--r--doc/project_services/img/jira_service.pngbin59082 -> 56834 bytes
-rw-r--r--doc/project_services/img/jira_service_close_issue.pngbin88433 -> 79569 bytes
-rw-r--r--doc/project_services/img/jira_service_page.pngbin49122 -> 36280 bytes
-rw-r--r--doc/project_services/img/jira_submit_gitlab_merge_request.pngbin63063 -> 51913 bytes
-rw-r--r--doc/project_services/img/jira_user_management_link.pngbin58211 -> 43095 bytes
-rw-r--r--doc/project_services/img/jira_workflow_screenshot.pngbin121534 -> 111093 bytes
-rw-r--r--doc/project_services/img/redmine_configuration.pngbin21061 -> 16973 bytes
-rw-r--r--doc/project_services/img/services_templates_redmine_example.pngbin17351 -> 13936 bytes
-rw-r--r--doc/project_services/project_services.md3
-rw-r--r--doc/public_access/public_access.md6
-rw-r--r--doc/raketasks/backup_hrz.pngbin21955 -> 8907 bytes
-rw-r--r--doc/raketasks/check_repos_output.pngbin73786 -> 35333 bytes
-rw-r--r--doc/raketasks/import.md3
-rw-r--r--doc/raketasks/maintenance.md19
-rw-r--r--doc/security/img/two_factor_authentication_settings.pngbin20399 -> 16807 bytes
-rw-r--r--doc/ssh/README.md10
-rw-r--r--doc/update/2.6-to-3.0.md4
-rw-r--r--doc/update/2.9-to-3.0.md5
-rw-r--r--doc/update/3.0-to-3.1.md5
-rw-r--r--doc/update/3.1-to-4.0.md5
-rw-r--r--doc/update/4.0-to-4.1.md5
-rw-r--r--doc/update/4.1-to-4.2.md10
-rw-r--r--doc/update/4.2-to-5.0.md9
-rw-r--r--doc/update/5.0-to-5.1.md10
-rw-r--r--doc/update/5.1-to-5.2.md18
-rw-r--r--doc/update/5.1-to-5.4.md18
-rw-r--r--doc/update/5.1-to-6.0.md18
-rw-r--r--doc/update/5.2-to-5.3.md18
-rw-r--r--doc/update/5.3-to-5.4.md18
-rw-r--r--doc/update/5.4-to-6.0.md16
-rw-r--r--doc/update/6.0-to-6.1.md19
-rw-r--r--doc/update/6.1-to-6.2.md19
-rw-r--r--doc/update/6.2-to-6.3.md17
-rw-r--r--doc/update/6.3-to-6.4.md17
-rw-r--r--doc/update/6.4-to-6.5.md21
-rw-r--r--doc/update/6.5-to-6.6.md20
-rw-r--r--doc/update/6.6-to-6.7.md16
-rw-r--r--doc/update/6.x-or-7.x-to-7.14.md7
-rw-r--r--doc/update/8.6-to-8.7.md2
-rw-r--r--doc/update/8.8-to-8.9.md37
-rw-r--r--doc/update/8.9-to-8.10.md191
-rw-r--r--doc/user/permissions.md131
-rw-r--r--doc/user/project/highlighting.md31
-rw-r--r--doc/user/project/img/labels_assign_label_in_new_issue.pngbin0 -> 31126 bytes
-rw-r--r--doc/user/project/img/labels_assign_label_sidebar.pngbin0 -> 31537 bytes
-rw-r--r--doc/user/project/img/labels_assign_label_sidebar_saved.pngbin0 -> 28396 bytes
-rw-r--r--doc/user/project/img/labels_default.pngbin0 -> 80403 bytes
-rw-r--r--doc/user/project/img/labels_description_tooltip.pngbin0 -> 22585 bytes
-rw-r--r--doc/user/project/img/labels_filter.pngbin0 -> 81536 bytes
-rw-r--r--doc/user/project/img/labels_filter_by_priority.pngbin0 -> 60849 bytes
-rw-r--r--doc/user/project/img/labels_generate.pngbin0 -> 31608 bytes
-rw-r--r--doc/user/project/img/labels_new_label.pngbin0 -> 43265 bytes
-rw-r--r--doc/user/project/img/labels_new_label_on_the_fly.pngbin0 -> 10416 bytes
-rw-r--r--doc/user/project/img/labels_new_label_on_the_fly_create.pngbin0 -> 16151 bytes
-rw-r--r--doc/user/project/img/labels_prioritize.pngbin0 -> 108751 bytes
-rw-r--r--doc/user/project/img/labels_subscribe.pngbin0 -> 11536 bytes
-rw-r--r--doc/user/project/labels.md147
-rw-r--r--doc/user/project/settings/img/import_export_download_export.pngbin0 -> 85600 bytes
-rw-r--r--doc/user/project/settings/img/import_export_export_button.pngbin0 -> 84637 bytes
-rw-r--r--doc/user/project/settings/img/import_export_mail_link.pngbin0 -> 44012 bytes
-rw-r--r--doc/user/project/settings/img/import_export_new_project.pngbin0 -> 43574 bytes
-rw-r--r--doc/user/project/settings/img/import_export_select_file.pngbin0 -> 46292 bytes
-rw-r--r--doc/user/project/settings/img/settings_edit_button.pngbin0 -> 19392 bytes
-rw-r--r--doc/user/project/settings/import_export.md73
-rw-r--r--doc/web_hooks/ssl.pngbin77165 -> 39120 bytes
-rw-r--r--doc/workflow/README.md2
-rw-r--r--doc/workflow/add-user/add-user.md26
-rw-r--r--doc/workflow/add-user/img/access_requests_management.pngbin0 -> 15686 bytes
-rw-r--r--doc/workflow/add-user/img/add_new_user_to_project_settings.pngbin22822 -> 18149 bytes
-rw-r--r--doc/workflow/add-user/img/add_user_email_accept.pngbin10833 -> 22877 bytes
-rw-r--r--doc/workflow/add-user/img/add_user_email_ready.pngbin16177 -> 40207 bytes
-rw-r--r--doc/workflow/add-user/img/add_user_email_search.pngbin15889 -> 45798 bytes
-rw-r--r--doc/workflow/add-user/img/add_user_give_permissions.pngbin22089 -> 56380 bytes
-rw-r--r--doc/workflow/add-user/img/add_user_import_members_from_another_project.pngbin18897 -> 38778 bytes
-rw-r--r--doc/workflow/add-user/img/add_user_imported_members.pngbin23897 -> 37835 bytes
-rw-r--r--doc/workflow/add-user/img/add_user_list_members.pngbin15732 -> 24337 bytes
-rw-r--r--doc/workflow/add-user/img/add_user_members_menu.pngbin8295 -> 42224 bytes
-rw-r--r--doc/workflow/add-user/img/add_user_search_people.pngbin13518 -> 39844 bytes
-rw-r--r--doc/workflow/add-user/img/request_access_button.pngbin0 -> 36588 bytes
-rw-r--r--doc/workflow/add-user/img/withdraw_access_request_button.pngbin0 -> 37960 bytes
-rw-r--r--doc/workflow/award_emoji.md45
-rw-r--r--doc/workflow/award_emoji.pngbin6620 -> 9939 bytes
-rw-r--r--doc/workflow/ci_mr.pngbin40065 -> 29571 bytes
-rw-r--r--doc/workflow/close_issue_mr.pngbin146292 -> 82595 bytes
-rw-r--r--doc/workflow/environment_branches.pngbin40210 -> 20745 bytes
-rw-r--r--doc/workflow/forking/branch_select.pngbin55352 -> 27299 bytes
-rw-r--r--doc/workflow/forking/merge_request.pngbin60597 -> 31560 bytes
-rw-r--r--doc/workflow/forking_workflow.md2
-rw-r--r--doc/workflow/four_stages.pngbin20934 -> 10003 bytes
-rw-r--r--doc/workflow/git_pull.pngbin167056 -> 94405 bytes
-rw-r--r--doc/workflow/gitdashflow.pngbin184726 -> 131491 bytes
-rw-r--r--doc/workflow/github_flow.pngbin20600 -> 10251 bytes
-rw-r--r--doc/workflow/gitlab_flow.pngbin90883 -> 70871 bytes
-rw-r--r--doc/workflow/good_commit.pngbin28433 -> 13131 bytes
-rw-r--r--doc/workflow/groups.md22
-rw-r--r--doc/workflow/groups/access_requests_management.pngbin0 -> 15829 bytes
-rw-r--r--doc/workflow/groups/add_member_to_group.pngbin138184 -> 78060 bytes
-rw-r--r--doc/workflow/groups/group_dashboard.pngbin107332 -> 59446 bytes
-rw-r--r--doc/workflow/groups/group_with_two_projects.pngbin129319 -> 73101 bytes
-rw-r--r--doc/workflow/groups/max_access_level.pngbin135354 -> 74947 bytes
-rw-r--r--doc/workflow/groups/new_group_button.pngbin185406 -> 108482 bytes
-rw-r--r--doc/workflow/groups/new_group_form.pngbin106491 -> 58860 bytes
-rw-r--r--doc/workflow/groups/other_group_sees_shared_project.pngbin118382 -> 64447 bytes
-rw-r--r--doc/workflow/groups/override_access_level.pngbin157193 -> 90122 bytes
-rw-r--r--doc/workflow/groups/project_members_via_group.pngbin151339 -> 86260 bytes
-rw-r--r--doc/workflow/groups/request_access_button.pngbin0 -> 49067 bytes
-rw-r--r--doc/workflow/groups/share_project_with_groups.pngbin118868 -> 65633 bytes
-rw-r--r--doc/workflow/groups/transfer_project.pngbin164958 -> 92115 bytes
-rw-r--r--doc/workflow/groups/withdraw_access_request_button.pngbin0 -> 49941 bytes
-rw-r--r--doc/workflow/img/award_emoji_comment_awarded.pngbin0 -> 64317 bytes
-rw-r--r--doc/workflow/img/award_emoji_comment_picker.pngbin0 -> 250861 bytes
-rw-r--r--doc/workflow/img/award_emoji_select.pngbin65985 -> 49296 bytes
-rw-r--r--doc/workflow/img/award_emoji_votes_least_popular.pngbin144501 -> 116715 bytes
-rw-r--r--doc/workflow/img/award_emoji_votes_most_popular.pngbin136577 -> 108775 bytes
-rw-r--r--doc/workflow/img/award_emoji_votes_sort_options.pngbin162251 -> 131659 bytes
-rw-r--r--doc/workflow/img/cherry_pick_changes_commit.pngbin353067 -> 304098 bytes
-rw-r--r--doc/workflow/img/cherry_pick_changes_commit_modal.pngbin312659 -> 264883 bytes
-rw-r--r--doc/workflow/img/cherry_pick_changes_mr.pngbin252085 -> 212267 bytes
-rw-r--r--doc/workflow/img/cherry_pick_changes_mr_modal.pngbin225569 -> 186597 bytes
-rw-r--r--doc/workflow/img/file_finder_find_button.pngbin30974 -> 25458 bytes
-rw-r--r--doc/workflow/img/file_finder_find_file.pngbin42658 -> 35114 bytes
-rw-r--r--doc/workflow/img/forking_workflow_choose_namespace.pngbin70405 -> 59114 bytes
-rw-r--r--doc/workflow/img/forking_workflow_fork_button.pngbin26438 -> 20750 bytes
-rw-r--r--doc/workflow/img/forking_workflow_path_taken_error.pngbin22380 -> 17978 bytes
-rw-r--r--doc/workflow/img/new_branch_from_issue.pngbin120622 -> 54607 bytes
-rw-r--r--doc/workflow/img/revert_changes_commit.pngbin360098 -> 233750 bytes
-rw-r--r--doc/workflow/img/revert_changes_commit_modal.pngbin327932 -> 205046 bytes
-rw-r--r--doc/workflow/img/revert_changes_mr.pngbin367881 -> 241051 bytes
-rw-r--r--doc/workflow/img/revert_changes_mr_modal.pngbin335010 -> 211022 bytes
-rw-r--r--doc/workflow/img/todo_list_item.pngbin0 -> 58912 bytes
-rw-r--r--doc/workflow/img/todos_add_todo_sidebar.pngbin0 -> 120265 bytes
-rw-r--r--doc/workflow/img/todos_icon.pngbin7394 -> 3843 bytes
-rw-r--r--doc/workflow/img/todos_index.pngbin184839 -> 152040 bytes
-rw-r--r--doc/workflow/img/todos_mark_done_sidebar.pngbin0 -> 121303 bytes
-rw-r--r--doc/workflow/img/web_editor_new_branch_dropdown.pngbin34233 -> 20436 bytes
-rw-r--r--doc/workflow/img/web_editor_new_branch_page.pngbin21618 -> 11245 bytes
-rw-r--r--doc/workflow/img/web_editor_new_directory_dialog.pngbin26145 -> 13339 bytes
-rw-r--r--doc/workflow/img/web_editor_new_directory_dropdown.pngbin33714 -> 20007 bytes
-rw-r--r--doc/workflow/img/web_editor_new_file_dropdown.pngbin34978 -> 20680 bytes
-rw-r--r--doc/workflow/img/web_editor_new_file_editor.pngbin128658 -> 66261 bytes
-rw-r--r--doc/workflow/img/web_editor_new_push_widget.pngbin11220 -> 7076 bytes
-rw-r--r--doc/workflow/img/web_editor_new_tag_dropdown.pngbin33753 -> 20080 bytes
-rw-r--r--doc/workflow/img/web_editor_new_tag_page.pngbin75536 -> 36610 bytes
-rw-r--r--doc/workflow/img/web_editor_start_new_merge_request.pngbin14352 -> 8596 bytes
-rw-r--r--doc/workflow/img/web_editor_upload_file_dialog.pngbin46219 -> 21502 bytes
-rw-r--r--doc/workflow/img/web_editor_upload_file_dropdown.pngbin34835 -> 20651 bytes
-rw-r--r--doc/workflow/importing/bitbucket_importer/bitbucket_import_select_project.pngbin16121 -> 15288 bytes
-rw-r--r--doc/workflow/importing/fogbugz_importer/fogbugz_import_finished.pngbin53276 -> 30266 bytes
-rw-r--r--doc/workflow/importing/fogbugz_importer/fogbugz_import_login.pngbin44444 -> 20797 bytes
-rw-r--r--doc/workflow/importing/fogbugz_importer/fogbugz_import_select_fogbogz.pngbin35415 -> 20526 bytes
-rw-r--r--doc/workflow/importing/fogbugz_importer/fogbugz_import_select_project.pngbin62552 -> 34836 bytes
-rw-r--r--doc/workflow/importing/fogbugz_importer/fogbugz_import_user_map.pngbin157856 -> 77208 bytes
-rw-r--r--doc/workflow/importing/gitlab_importer/importer.pngbin40778 -> 18366 bytes
-rw-r--r--doc/workflow/importing/gitlab_importer/new_project_page.pngbin72663 -> 33589 bytes
-rw-r--r--doc/workflow/importing/img/import_projects_from_github_importer.pngbin28033 -> 22711 bytes
-rw-r--r--doc/workflow/importing/img/import_projects_from_github_new_project_page.pngbin17225 -> 13668 bytes
-rw-r--r--doc/workflow/importing/import_projects_from_github.md18
-rw-r--r--doc/workflow/labels.md17
-rw-r--r--doc/workflow/labels/label1.pngbin5846 -> 0 bytes
-rw-r--r--doc/workflow/labels/label2.pngbin16931 -> 0 bytes
-rw-r--r--doc/workflow/labels/label3.pngbin19360 -> 0 bytes
-rw-r--r--doc/workflow/merge_commits.pngbin41422 -> 22181 bytes
-rw-r--r--doc/workflow/merge_request.pngbin169503 -> 98070 bytes
-rw-r--r--doc/workflow/merge_requests/commit_compare.pngbin110376 -> 65010 bytes
-rw-r--r--doc/workflow/merge_requests/merge_request_diff.pngbin166226 -> 103239 bytes
-rw-r--r--doc/workflow/merge_requests/merge_request_diff_without_whitespace.pngbin121476 -> 71896 bytes
-rw-r--r--doc/workflow/merge_when_build_succeeds/enable.pngbin151112 -> 68769 bytes
-rw-r--r--doc/workflow/merge_when_build_succeeds/status.pngbin180318 -> 82655 bytes
-rw-r--r--doc/workflow/messy_flow.pngbin33829 -> 19314 bytes
-rw-r--r--doc/workflow/milestones/form.pngbin88591 -> 84872 bytes
-rw-r--r--doc/workflow/milestones/group_form.pngbin77087 -> 74429 bytes
-rw-r--r--doc/workflow/mr_inline_comments.pngbin193311 -> 117313 bytes
-rw-r--r--doc/workflow/notifications.md2
-rw-r--r--doc/workflow/notifications/settings.pngbin90986 -> 59256 bytes
-rw-r--r--doc/workflow/production_branch.pngbin21716 -> 10946 bytes
-rw-r--r--doc/workflow/protected_branches.md30
-rw-r--r--doc/workflow/protected_branches/protected_branches1.pngbin170113 -> 195061 bytes
-rw-r--r--doc/workflow/protected_branches/protected_branches2.pngbin25851 -> 41179 bytes
-rw-r--r--doc/workflow/protected_branches/protected_branches3.pngbin0 -> 110160 bytes
-rw-r--r--doc/workflow/rebase.pngbin123041 -> 68976 bytes
-rw-r--r--doc/workflow/release_branches.pngbin44173 -> 22163 bytes
-rw-r--r--doc/workflow/releases/new_tag.pngbin154755 -> 87330 bytes
-rw-r--r--doc/workflow/releases/tags.pngbin165449 -> 93016 bytes
-rw-r--r--doc/workflow/remove_checkbox.pngbin22272 -> 12339 bytes
-rw-r--r--doc/workflow/share_with_group.pngbin53784 -> 50450 bytes
-rw-r--r--doc/workflow/shortcuts.pngbin90936 -> 108209 bytes
-rw-r--r--doc/workflow/todos.md59
-rw-r--r--doc/workflow/wip_merge_requests/blocked_accept_button.pngbin65231 -> 32720 bytes
-rw-r--r--doc/workflow/wip_merge_requests/mark_as_wip.pngbin41549 -> 21640 bytes
-rw-r--r--doc/workflow/wip_merge_requests/unmark_as_wip.pngbin32151 -> 16606 bytes
-rw-r--r--features/admin/groups.feature7
-rw-r--r--features/admin/projects.feature4
-rw-r--r--features/dashboard/dashboard.feature1
-rw-r--r--features/dashboard/group.feature44
-rw-r--r--features/dashboard/new_project.feature2
-rw-r--r--features/project/active_tab.feature30
-rw-r--r--features/project/commits/commits.feature5
-rw-r--r--features/project/commits/diff_comments.feature4
-rw-r--r--features/project/issues/issues.feature2
-rw-r--r--features/project/merge_requests.feature7
-rw-r--r--features/project/shortcuts.feature6
-rw-r--r--features/search.feature8
-rw-r--r--features/steps/admin/groups.rb9
-rw-r--r--features/steps/admin/projects.rb9
-rw-r--r--features/steps/dashboard/group.rb42
-rw-r--r--features/steps/dashboard/help.rb2
-rw-r--r--features/steps/dashboard/new_project.rb13
-rw-r--r--features/steps/explore/projects.rb3
-rw-r--r--features/steps/groups.rb2
-rw-r--r--features/steps/profile/notifications.rb4
-rw-r--r--features/steps/profile/profile.rb7
-rw-r--r--features/steps/project/archived.rb1
-rw-r--r--features/steps/project/builds/artifacts.rb14
-rw-r--r--features/steps/project/commits/commits.rb19
-rw-r--r--features/steps/project/forked_merge_requests.rb2
-rw-r--r--features/steps/project/issues/award_emoji.rb4
-rw-r--r--features/steps/project/issues/issues.rb5
-rw-r--r--features/steps/project/merge_requests.rb5
-rw-r--r--features/steps/project/network_graph.rb20
-rw-r--r--features/steps/project/project.rb4
-rw-r--r--features/steps/project/project_find_file.rb5
-rw-r--r--features/steps/project/source/browse_files.rb14
-rw-r--r--features/steps/shared/diff_note.rb33
-rw-r--r--features/steps/shared/issuable.rb1
-rw-r--r--features/steps/shared/project.rb5
-rw-r--r--features/steps/shared/project_tab.rb4
-rw-r--r--features/steps/snippet_search.rb1
-rw-r--r--features/support/env.rb7
-rw-r--r--fixtures/emojis/aliases.json191
-rw-r--r--fixtures/emojis/digests.json6214
-rw-r--r--fixtures/emojis/index.json13379
-rw-r--r--lib/api/api.rb5
-rw-r--r--lib/api/award_emoji.rb5
-rw-r--r--lib/api/branches.rb39
-rw-r--r--lib/api/builds.rb7
-rw-r--r--lib/api/commit_statuses.rb2
-rw-r--r--lib/api/entities.rb107
-rw-r--r--lib/api/gitignores.rb29
-rw-r--r--lib/api/group_members.rb2
-rw-r--r--lib/api/helpers.rb9
-rw-r--r--lib/api/internal.rb48
-rw-r--r--lib/api/issues.rb45
-rw-r--r--lib/api/license_templates.rb (renamed from lib/api/licenses.rb)4
-rw-r--r--lib/api/merge_requests.rb4
-rw-r--r--lib/api/milestones.rb1
-rw-r--r--lib/api/project_hooks.rb1
-rw-r--r--lib/api/project_members.rb1
-rw-r--r--lib/api/projects.rb8
-rw-r--r--lib/api/runners.rb12
-rw-r--r--lib/api/services.rb1
-rw-r--r--lib/api/tags.rb6
-rw-r--r--lib/api/templates.rb36
-rw-r--r--lib/api/todos.rb82
-rw-r--r--lib/backup/repository.rb26
-rw-r--r--lib/banzai.rb8
-rw-r--r--lib/banzai/filter/abstract_reference_filter.rb14
-rw-r--r--lib/banzai/filter/autolink_filter.rb2
-rw-r--r--lib/banzai/filter/blockquote_fence_filter.rb71
-rw-r--r--lib/banzai/filter/emoji_filter.rb4
-rw-r--r--lib/banzai/filter/image_link_filter.rb9
-rw-r--r--lib/banzai/filter/issue_reference_filter.rb30
-rw-r--r--lib/banzai/filter/label_reference_filter.rb8
-rw-r--r--lib/banzai/filter/redactor_filter.rb29
-rw-r--r--lib/banzai/filter/reference_filter.rb2
-rw-r--r--lib/banzai/filter/relative_link_filter.rb51
-rw-r--r--lib/banzai/filter/syntax_highlight_filter.rb24
-rw-r--r--lib/banzai/filter/user_reference_filter.rb10
-rw-r--r--lib/banzai/filter/wiki_link_filter.rb1
-rw-r--r--lib/banzai/note_renderer.rb22
-rw-r--r--lib/banzai/object_renderer.rb86
-rw-r--r--lib/banzai/pipeline/full_pipeline.rb1
-rw-r--r--lib/banzai/pipeline/pre_process_pipeline.rb3
-rw-r--r--lib/banzai/pipeline/relative_link_pipeline.rb11
-rw-r--r--lib/banzai/redactor.rb82
-rw-r--r--lib/banzai/reference_parser/base_parser.rb36
-rw-r--r--lib/banzai/reference_parser/user_parser.rb5
-rw-r--r--lib/banzai/renderer.rb68
-rw-r--r--lib/ci/api/builds.rb3
-rw-r--r--lib/ci/api/runners.rb9
-rw-r--r--lib/ci/charts.rb1
-rw-r--r--lib/ci/gitlab_ci_yaml_processor.rb173
-rw-r--r--lib/container_registry/client.rb69
-rw-r--r--lib/container_registry/tag.rb3
-rw-r--r--lib/disable_email_interceptor.rb1
-rw-r--r--lib/gitlab.rb7
-rw-r--r--lib/gitlab/access.rb10
-rw-r--r--lib/gitlab/asciidoc.rb1
-rw-r--r--lib/gitlab/award_emoji.rb35
-rw-r--r--lib/gitlab/backend/grack_auth.rb7
-rw-r--r--lib/gitlab/backend/shell.rb98
-rw-r--r--lib/gitlab/blame.rb3
-rw-r--r--lib/gitlab/checks/change_access.rb96
-rw-r--r--lib/gitlab/checks/force_push.rb17
-rw-r--r--lib/gitlab/checks/matching_merge_request.rb18
-rw-r--r--lib/gitlab/ci/config.rb13
-rw-r--r--lib/gitlab/ci/config/node/boolean.rb18
-rw-r--r--lib/gitlab/ci/config/node/cache.rb27
-rw-r--r--lib/gitlab/ci/config/node/configurable.rb42
-rw-r--r--lib/gitlab/ci/config/node/entry.rb61
-rw-r--r--lib/gitlab/ci/config/node/factory.rb29
-rw-r--r--lib/gitlab/ci/config/node/global.rb30
-rw-r--r--lib/gitlab/ci/config/node/image.rb18
-rw-r--r--lib/gitlab/ci/config/node/key.rb18
-rw-r--r--lib/gitlab/ci/config/node/legacy_validation_helpers.rb (renamed from lib/gitlab/ci/config/node/validation_helpers.rb)2
-rw-r--r--lib/gitlab/ci/config/node/null.rb27
-rw-r--r--lib/gitlab/ci/config/node/paths.rb18
-rw-r--r--lib/gitlab/ci/config/node/script.rb17
-rw-r--r--lib/gitlab/ci/config/node/services.rb18
-rw-r--r--lib/gitlab/ci/config/node/stages.rb22
-rw-r--r--lib/gitlab/ci/config/node/undefined.rb30
-rw-r--r--lib/gitlab/ci/config/node/validatable.rb29
-rw-r--r--lib/gitlab/ci/config/node/validator.rb41
-rw-r--r--lib/gitlab/ci/config/node/validators.rb70
-rw-r--r--lib/gitlab/ci/config/node/variables.rb22
-rw-r--r--lib/gitlab/current_settings.rb11
-rw-r--r--lib/gitlab/database/migration_helpers.rb10
-rw-r--r--lib/gitlab/diff/diff_refs.rb36
-rw-r--r--lib/gitlab/diff/file.rb101
-rw-r--r--lib/gitlab/diff/highlight.rb31
-rw-r--r--lib/gitlab/diff/inline_diff.rb52
-rw-r--r--lib/gitlab/diff/line.rb16
-rw-r--r--lib/gitlab/diff/line_mapper.rb64
-rw-r--r--lib/gitlab/diff/parallel_diff.rb137
-rw-r--r--lib/gitlab/diff/parser.rb1
-rw-r--r--lib/gitlab/diff/position.rb155
-rw-r--r--lib/gitlab/diff/position_tracer.rb168
-rw-r--r--lib/gitlab/email/message/repository_push.rb8
-rw-r--r--lib/gitlab/email/receiver.rb10
-rw-r--r--lib/gitlab/emoji.rb21
-rw-r--r--lib/gitlab/force_push_check.rb15
-rw-r--r--lib/gitlab/git/hook.rb21
-rw-r--r--lib/gitlab/git_access.rb149
-rw-r--r--lib/gitlab/git_access_wiki.rb2
-rw-r--r--lib/gitlab/github_import/branch_formatter.rb12
-rw-r--r--lib/gitlab/github_import/client.rb58
-rw-r--r--lib/gitlab/github_import/importer.rb6
-rw-r--r--lib/gitlab/github_import/pull_request_formatter.rb4
-rw-r--r--lib/gitlab/gitignore.rb56
-rw-r--r--lib/gitlab/gitlab_import/importer.rb52
-rw-r--r--lib/gitlab/gon_helper.rb3
-rw-r--r--lib/gitlab/graphs/commits.rb2
-rw-r--r--lib/gitlab/highlight.rb42
-rw-r--r--lib/gitlab/import_export.rb9
-rw-r--r--lib/gitlab/import_export/avatar_restorer.rb31
-rw-r--r--lib/gitlab/import_export/avatar_saver.rb31
-rw-r--r--lib/gitlab/import_export/command_line_util.rb12
-rw-r--r--lib/gitlab/import_export/file_importer.rb6
-rw-r--r--lib/gitlab/import_export/import_export.yml21
-rw-r--r--lib/gitlab/import_export/importer.rb23
-rw-r--r--lib/gitlab/import_export/members_mapper.rb1
-rw-r--r--lib/gitlab/import_export/project_creator.rb1
-rw-r--r--lib/gitlab/import_export/project_tree_restorer.rb15
-rw-r--r--lib/gitlab/import_export/reader.rb2
-rw-r--r--lib/gitlab/import_export/relation_factory.rb19
-rw-r--r--lib/gitlab/import_export/repo_restorer.rb9
-rw-r--r--lib/gitlab/import_export/repo_saver.rb2
-rw-r--r--lib/gitlab/import_export/saver.rb6
-rw-r--r--lib/gitlab/import_export/shared.rb1
-rw-r--r--lib/gitlab/import_export/uploads_saver.rb7
-rw-r--r--lib/gitlab/import_export/version_checker.rb1
-rw-r--r--lib/gitlab/import_export/version_saver.rb1
-rw-r--r--lib/gitlab/import_export/wiki_repo_saver.rb1
-rw-r--r--lib/gitlab/import_sources.rb2
-rw-r--r--lib/gitlab/key_fingerprint.rb2
-rw-r--r--lib/gitlab/lfs/response.rb10
-rw-r--r--lib/gitlab/lfs/router.rb9
-rw-r--r--lib/gitlab/metrics/method_call.rb6
-rw-r--r--lib/gitlab/metrics/metric.rb21
-rw-r--r--lib/gitlab/metrics/sidekiq_middleware.rb2
-rw-r--r--lib/gitlab/metrics/subscribers/rails_cache.rb22
-rw-r--r--lib/gitlab/metrics/system.rb20
-rw-r--r--lib/gitlab/metrics/transaction.rb6
-rw-r--r--lib/gitlab/o_auth/auth_hash.rb2
-rw-r--r--lib/gitlab/o_auth/user.rb4
-rw-r--r--lib/gitlab/other_markup.rb1
-rw-r--r--lib/gitlab/protocol_access.rb13
-rw-r--r--lib/gitlab/regex.rb6
-rw-r--r--lib/gitlab/saml/auth_hash.rb2
-rw-r--r--lib/gitlab/saml/config.rb2
-rw-r--r--lib/gitlab/saml/user.rb1
-rw-r--r--lib/gitlab/sidekiq_middleware/memory_killer.rb8
-rw-r--r--lib/gitlab/template/base_template.rb67
-rw-r--r--lib/gitlab/template/gitignore.rb22
-rw-r--r--lib/gitlab/template/gitlab_ci_yml.rb27
-rw-r--r--lib/gitlab/timeless.rb16
-rw-r--r--lib/gitlab/url_sanitizer.rb12
-rw-r--r--lib/gitlab/user_access.rb48
-rw-r--r--lib/gitlab/workhorse.rb31
-rw-r--r--lib/rouge/formatters/html_gitlab.rb172
-rw-r--r--lib/support/nginx/gitlab7
-rw-r--r--lib/support/nginx/gitlab-ssl7
-rw-r--r--lib/tasks/gemojione.rake19
-rw-r--r--lib/tasks/gitlab/backup.rake3
-rw-r--r--lib/tasks/gitlab/check.rake167
-rw-r--r--lib/tasks/gitlab/cleanup.rake71
-rw-r--r--lib/tasks/gitlab/import.rake96
-rw-r--r--lib/tasks/gitlab/import_export.rake13
-rw-r--r--lib/tasks/gitlab/info.rake5
-rw-r--r--lib/tasks/gitlab/list_repos.rake2
-rw-r--r--lib/tasks/gitlab/shell.rake12
-rw-r--r--lib/tasks/gitlab/task_helpers.rake12
-rw-r--r--lib/tasks/gitlab/track_deployment.rake9
-rw-r--r--lib/tasks/gitlab/update_gitignore.rake46
-rw-r--r--lib/tasks/gitlab/update_templates.rake54
-rw-r--r--lib/tasks/test.rake4
-rw-r--r--lib/uploaded_file.rb1
-rw-r--r--public/apple-touch-icon-precomposed.pngbin11097 -> 9509 bytes
-rw-r--r--public/apple-touch-icon.pngbin11097 -> 9509 bytes
-rw-r--r--rubocop/cop/migration/add_index.rb46
-rw-r--r--rubocop/cop/migration/column_with_default.rb50
-rw-r--r--rubocop/migration_helpers.rb10
-rw-r--r--rubocop/rubocop.rb3
-rw-r--r--spec/controllers/admin/impersonations_controller_spec.rb6
-rw-r--r--spec/controllers/admin/projects_controller_spec.rb4
-rw-r--r--spec/controllers/admin/spam_logs_controller_spec.rb6
-rw-r--r--spec/controllers/admin/users_controller_spec.rb2
-rw-r--r--spec/controllers/application_controller_spec.rb9
-rw-r--r--spec/controllers/autocomplete_controller_spec.rb10
-rw-r--r--spec/controllers/commit_controller_spec.rb246
-rw-r--r--spec/controllers/groups/group_members_controller_spec.rb24
-rw-r--r--spec/controllers/health_check_controller_spec.rb10
-rw-r--r--spec/controllers/help_controller_spec.rb22
-rw-r--r--spec/controllers/import/github_controller_spec.rb43
-rw-r--r--spec/controllers/invites_controller_spec.rb4
-rw-r--r--spec/controllers/namespaces_controller_spec.rb4
-rw-r--r--spec/controllers/notification_settings_controller_spec.rb77
-rw-r--r--spec/controllers/oauth/applications_controller_spec.rb4
-rw-r--r--spec/controllers/profiles/accounts_controller_spec.rb3
-rw-r--r--spec/controllers/projects/blob_controller_spec.rb40
-rw-r--r--spec/controllers/projects/branches_controller_spec.rb12
-rw-r--r--spec/controllers/projects/commit_controller_spec.rb289
-rw-r--r--spec/controllers/projects/compare_controller_spec.rb69
-rw-r--r--spec/controllers/projects/forks_controller_spec.rb2
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb12
-rw-r--r--spec/controllers/projects/labels_controller_spec.rb1
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb286
-rw-r--r--spec/controllers/projects/notes_controller_spec.rb4
-rw-r--r--spec/controllers/projects/project_members_controller_spec.rb26
-rw-r--r--spec/controllers/projects/raw_controller_spec.rb8
-rw-r--r--spec/controllers/projects/repositories_controller_spec.rb3
-rw-r--r--spec/controllers/projects/snippets_controller_spec.rb16
-rw-r--r--spec/controllers/projects/todo_controller_spec.rb90
-rw-r--r--spec/controllers/projects/tree_controller_spec.rb3
-rw-r--r--spec/controllers/projects/uploads_controller_spec.rb24
-rw-r--r--spec/controllers/projects_controller_spec.rb53
-rw-r--r--spec/controllers/registrations_controller_spec.rb3
-rw-r--r--spec/controllers/snippets_controller_spec.rb28
-rw-r--r--spec/controllers/uploads_controller_spec.rb28
-rw-r--r--spec/controllers/users_controller_spec.rb11
-rw-r--r--spec/factories/ci/builds.rb12
-rw-r--r--spec/factories/ci/pipelines.rb (renamed from spec/factories/ci/commits.rb)0
-rw-r--r--spec/factories/notes.rb33
-rw-r--r--spec/factories/notification_settings.rb8
-rw-r--r--spec/factories/projects.rb2
-rw-r--r--spec/factories/todos.rb8
-rw-r--r--spec/features/admin/admin_abuse_reports_spec.rb30
-rw-r--r--spec/features/admin/admin_builds_spec.rb36
-rw-r--r--spec/features/admin/admin_disables_git_access_protocol_spec.rb66
-rw-r--r--spec/features/admin/admin_hooks_spec.rb2
-rw-r--r--spec/features/admin/admin_runners_spec.rb60
-rw-r--r--spec/features/admin/admin_system_info_spec.rb17
-rw-r--r--spec/features/admin/admin_users_spec.rb8
-rw-r--r--spec/features/atom/users_spec.rb2
-rw-r--r--spec/features/builds_spec.rb46
-rw-r--r--spec/features/compare_spec.rb42
-rw-r--r--spec/features/container_registry_spec.rb3
-rw-r--r--spec/features/dashboard/user_filters_projects_spec.rb1
-rw-r--r--spec/features/environments_spec.rb44
-rw-r--r--spec/features/expand_collapse_diffs_spec.rb248
-rw-r--r--spec/features/gitlab_flavored_markdown_spec.rb2
-rw-r--r--spec/features/groups/members/last_owner_cannot_leave_group_spec.rb16
-rw-r--r--spec/features/groups/members/member_cannot_request_access_to_his_project_spec.rb16
-rw-r--r--spec/features/groups/members/member_leaves_group_spec.rb21
-rw-r--r--spec/features/groups/members/owner_manages_access_requests_spec.rb5
-rw-r--r--spec/features/groups/members/user_requests_access_spec.rb24
-rw-r--r--spec/features/groups_spec.rb20
-rw-r--r--spec/features/help_pages_spec.rb2
-rw-r--r--spec/features/issues/bulk_assignment_labels_spec.rb151
-rw-r--r--spec/features/issues/filter_issues_spec.rb10
-rw-r--r--spec/features/issues_spec.rb8
-rw-r--r--spec/features/login_spec.rb43
-rw-r--r--spec/features/merge_requests/created_from_fork_spec.rb2
-rw-r--r--spec/features/merge_requests/diffs_spec.rb25
-rw-r--r--spec/features/merge_requests/merge_when_build_succeeds_spec.rb4
-rw-r--r--spec/features/merge_requests/only_allow_merge_if_build_succeeds.rb2
-rw-r--r--spec/features/notes_on_merge_requests_spec.rb31
-rw-r--r--spec/features/pipelines_spec.rb24
-rw-r--r--spec/features/projects/badges/list_spec.rb8
-rw-r--r--spec/features/projects/commit/builds_spec.rb1
-rw-r--r--spec/features/projects/commits/cherry_pick_spec.rb1
-rw-r--r--spec/features/projects/files/gitignore_dropdown_spec.rb1
-rw-r--r--spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb31
-rw-r--r--spec/features/projects/import_export/import_file_spec.rb19
-rw-r--r--spec/features/projects/import_export/test_project_export.tar.gzbin345686 -> 687442 bytes
-rw-r--r--spec/features/projects/labels/issues_sorted_by_priority_spec.rb3
-rw-r--r--spec/features/projects/labels/update_prioritization_spec.rb2
-rw-r--r--spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb3
-rw-r--r--spec/features/projects/members/master_manages_access_requests_spec.rb4
-rw-r--r--spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb16
-rw-r--r--spec/features/projects/members/member_leaves_project_spec.rb19
-rw-r--r--spec/features/projects/members/owner_cannot_leave_project_spec.rb16
-rw-r--r--spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb16
-rw-r--r--spec/features/projects/members/user_requests_access_spec.rb9
-rw-r--r--spec/features/projects_spec.rb16
-rw-r--r--spec/features/protected_branches_spec.rb84
-rw-r--r--spec/features/search_spec.rb7
-rw-r--r--spec/features/security/group/internal_access_spec.rb1
-rw-r--r--spec/features/security/group/private_access_spec.rb1
-rw-r--r--spec/features/security/group/public_access_spec.rb1
-rw-r--r--spec/features/security/project/internal_access_spec.rb157
-rw-r--r--spec/features/security/project/private_access_spec.rb121
-rw-r--r--spec/features/security/project/public_access_spec.rb50
-rw-r--r--spec/features/signup_spec.rb2
-rw-r--r--spec/features/tags/master_deletes_tag_spec.rb1
-rw-r--r--spec/features/u2f_spec.rb55
-rw-r--r--spec/features/users_spec.rb1
-rw-r--r--spec/finders/group_projects_finder_spec.rb2
-rw-r--r--spec/finders/notes_finder_spec.rb2
-rw-r--r--spec/finders/snippets_finder_spec.rb1
-rw-r--r--spec/fixtures/blockquote_fence_after.md115
-rw-r--r--spec/fixtures/blockquote_fence_before.md131
-rw-r--r--spec/fixtures/container_registry/tag_manifest.json17
-rw-r--r--spec/fixtures/dk.pngbin1143 -> 1062 bytes
-rw-r--r--spec/fixtures/parallel_diff_result.yml526
-rw-r--r--spec/helpers/application_helper_spec.rb45
-rw-r--r--spec/helpers/blob_helper_spec.rb32
-rw-r--r--spec/helpers/ci_status_helper_spec.rb10
-rw-r--r--spec/helpers/diff_helper_spec.rb34
-rw-r--r--spec/helpers/events_helper_spec.rb2
-rw-r--r--spec/helpers/notes_helper_spec.rb46
-rw-r--r--spec/helpers/projects_helper_spec.rb10
-rw-r--r--spec/helpers/time_helper_spec.rb25
-rw-r--r--spec/helpers/visibility_level_helper_spec.rb7
-rw-r--r--spec/initializers/6_validations_spec.rb41
-rw-r--r--spec/initializers/settings_spec.rb3
-rw-r--r--spec/initializers/trusted_proxies_spec.rb12
-rw-r--r--spec/javascripts/application_spec.js.coffee2
-rw-r--r--spec/javascripts/awards_handler_spec.js.coffee1
-rw-r--r--spec/javascripts/fixtures/emoji_menu.coffee2
-rw-r--r--spec/javascripts/fixtures/issues_show.html.haml2
-rw-r--r--spec/javascripts/issue_spec.js.coffee13
-rw-r--r--spec/javascripts/project_title_spec.js.coffee4
-rw-r--r--spec/javascripts/search_autocomplete_spec.js.coffee4
-rw-r--r--spec/javascripts/u2f/authenticate_spec.coffee3
-rw-r--r--spec/javascripts/u2f/register_spec.js.coffee1
-rw-r--r--spec/lib/banzai/filter/autolink_filter_spec.rb20
-rw-r--r--spec/lib/banzai/filter/blockquote_fence_filter_spec.rb14
-rw-r--r--spec/lib/banzai/filter/image_link_filter_spec.rb5
-rw-r--r--spec/lib/banzai/filter/issue_reference_filter_spec.rb49
-rw-r--r--spec/lib/banzai/filter/label_reference_filter_spec.rb142
-rw-r--r--spec/lib/banzai/filter/relative_link_filter_spec.rb7
-rw-r--r--spec/lib/banzai/filter/syntax_highlight_filter_spec.rb34
-rw-r--r--spec/lib/banzai/note_renderer_spec.rb25
-rw-r--r--spec/lib/banzai/object_renderer_spec.rb146
-rw-r--r--spec/lib/banzai/pipeline/wiki_pipeline_spec.rb1
-rw-r--r--spec/lib/banzai/redactor_spec.rb73
-rw-r--r--spec/lib/banzai/reference_parser/base_parser_spec.rb75
-rw-r--r--spec/lib/ci/charts_spec.rb1
-rw-r--r--spec/lib/ci/gitlab_ci_yaml_processor_spec.rb215
-rw-r--r--spec/lib/container_registry/blob_spec.rb52
-rw-r--r--spec/lib/container_registry/tag_spec.rb49
-rw-r--r--spec/lib/gitlab/asciidoc_spec.rb3
-rw-r--r--spec/lib/gitlab/award_emoji_spec.rb15
-rw-r--r--spec/lib/gitlab/backend/shell_spec.rb28
-rw-r--r--spec/lib/gitlab/badge/build_spec.rb2
-rw-r--r--spec/lib/gitlab/bitbucket_import/client_spec.rb2
-rw-r--r--spec/lib/gitlab/build_data_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb1
-rw-r--r--spec/lib/gitlab/ci/config/node/boolean_spec.rb34
-rw-r--r--spec/lib/gitlab/ci/config/node/cache_spec.rb60
-rw-r--r--spec/lib/gitlab/ci/config/node/configurable_spec.rb48
-rw-r--r--spec/lib/gitlab/ci/config/node/factory_spec.rb37
-rw-r--r--spec/lib/gitlab/ci/config/node/global_spec.rb186
-rw-r--r--spec/lib/gitlab/ci/config/node/image_spec.rb46
-rw-r--r--spec/lib/gitlab/ci/config/node/key_spec.rb34
-rw-r--r--spec/lib/gitlab/ci/config/node/null_spec.rb23
-rw-r--r--spec/lib/gitlab/ci/config/node/paths_spec.rb34
-rw-r--r--spec/lib/gitlab/ci/config/node/script_spec.rb18
-rw-r--r--spec/lib/gitlab/ci/config/node/services_spec.rb40
-rw-r--r--spec/lib/gitlab/ci/config/node/stages_spec.rb46
-rw-r--r--spec/lib/gitlab/ci/config/node/undefined_spec.rb40
-rw-r--r--spec/lib/gitlab/ci/config/node/validatable_spec.rb50
-rw-r--r--spec/lib/gitlab/ci/config/node/validator_spec.rb55
-rw-r--r--spec/lib/gitlab/ci/config/node/variables_spec.rb48
-rw-r--r--spec/lib/gitlab/ci/config_spec.rb44
-rw-r--r--spec/lib/gitlab/current_settings_spec.rb36
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb4
-rw-r--r--spec/lib/gitlab/diff/file_spec.rb20
-rw-r--r--spec/lib/gitlab/diff/highlight_spec.rb10
-rw-r--r--spec/lib/gitlab/diff/inline_diff_marker_spec.rb1
-rw-r--r--spec/lib/gitlab/diff/inline_diff_spec.rb28
-rw-r--r--spec/lib/gitlab/diff/line_mapper_spec.rb137
-rw-r--r--spec/lib/gitlab/diff/parallel_diff_spec.rb3
-rw-r--r--spec/lib/gitlab/diff/parser_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/position_spec.rb341
-rw-r--r--spec/lib/gitlab/diff/position_tracer_spec.rb1759
-rw-r--r--spec/lib/gitlab/fogbugz_import/client_spec.rb1
-rw-r--r--spec/lib/gitlab/git/hook_spec.rb70
-rw-r--r--spec/lib/gitlab/git_access_spec.rb321
-rw-r--r--spec/lib/gitlab/git_access_wiki_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/branch_formatter_spec.rb19
-rw-r--r--spec/lib/gitlab/github_import/client_spec.rb21
-rw-r--r--spec/lib/gitlab/github_import/label_formatter_spec.rb1
-rw-r--r--spec/lib/gitlab/github_import/pull_request_formatter_spec.rb18
-rw-r--r--spec/lib/gitlab/gitlab_import/importer_spec.rb53
-rw-r--r--spec/lib/gitlab/google_code_import/importer_spec.rb1
-rw-r--r--spec/lib/gitlab/graphs/commits_spec.rb39
-rw-r--r--spec/lib/gitlab/highlight_spec.rb27
-rw-r--r--spec/lib/gitlab/import_export/avatar_restorer_spec.rb25
-rw-r--r--spec/lib/gitlab/import_export/avatar_saver_spec.rb27
-rw-r--r--spec/lib/gitlab/import_export/import_export_spec.rb21
-rw-r--r--spec/lib/gitlab/import_export/members_mapper_spec.rb1
-rw-r--r--spec/lib/gitlab/import_export/project.json6117
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb42
-rw-r--r--spec/lib/gitlab/import_export/project_tree_saver_spec.rb20
-rw-r--r--spec/lib/gitlab/import_export/reader_spec.rb3
-rw-r--r--spec/lib/gitlab/import_export/repo_bundler_spec.rb1
-rw-r--r--spec/lib/gitlab/import_export/wiki_repo_bundler_spec.rb1
-rw-r--r--spec/lib/gitlab/ldap/access_spec.rb2
-rw-r--r--spec/lib/gitlab/ldap/auth_hash_spec.rb1
-rw-r--r--spec/lib/gitlab/ldap/user_spec.rb2
-rw-r--r--spec/lib/gitlab/lfs/lfs_router_spec.rb765
-rw-r--r--spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb17
-rw-r--r--spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb103
-rw-r--r--spec/lib/gitlab/metrics/system_spec.rb16
-rw-r--r--spec/lib/gitlab/note_data_builder_spec.rb4
-rw-r--r--spec/lib/gitlab/o_auth/user_spec.rb24
-rw-r--r--spec/lib/gitlab/popen_spec.rb6
-rw-r--r--spec/lib/gitlab/push_data_builder_spec.rb1
-rw-r--r--spec/lib/gitlab/saml/user_spec.rb19
-rw-r--r--spec/lib/gitlab/template/gitignore_spec.rb (renamed from spec/lib/gitlab/gitignore_spec.rb)6
-rw-r--r--spec/lib/gitlab/url_builder_spec.rb8
-rw-r--r--spec/lib/gitlab/url_sanitizer_spec.rb7
-rw-r--r--spec/lib/gitlab/user_access_spec.rb88
-rw-r--r--spec/lib/gitlab_spec.rb6
-rw-r--r--spec/mailers/notify_spec.rb76
-rw-r--r--spec/models/application_setting_spec.rb10
-rw-r--r--spec/models/build_spec.rb314
-rw-r--r--spec/models/ci/pipeline_spec.rb99
-rw-r--r--spec/models/ci/runner_spec.rb241
-rw-r--r--spec/models/ci/variable_spec.rb2
-rw-r--r--spec/models/commit_spec.rb12
-rw-r--r--spec/models/commit_status_spec.rb57
-rw-r--r--spec/models/concerns/access_requestable_spec.rb4
-rw-r--r--spec/models/concerns/issuable_spec.rb15
-rw-r--r--spec/models/concerns/mentionable_spec.rb39
-rw-r--r--spec/models/concerns/participable_spec.rb10
-rw-r--r--spec/models/concerns/strip_attribute_spec.rb1
-rw-r--r--spec/models/deployment_spec.rb1
-rw-r--r--spec/models/diff_note_spec.rb191
-rw-r--r--spec/models/email_spec.rb2
-rw-r--r--spec/models/event_spec.rb32
-rw-r--r--spec/models/forked_project_link_spec.rb5
-rw-r--r--spec/models/generic_commit_status_spec.rb10
-rw-r--r--spec/models/global_milestone_spec.rb6
-rw-r--r--spec/models/group_spec.rb39
-rw-r--r--spec/models/identity_spec.rb1
-rw-r--r--spec/models/key_spec.rb2
-rw-r--r--spec/models/label_spec.rb13
-rw-r--r--spec/models/legacy_diff_note_spec.rb24
-rw-r--r--spec/models/member_spec.rb32
-rw-r--r--spec/models/members/group_member_spec.rb10
-rw-r--r--spec/models/members/project_member_spec.rb15
-rw-r--r--spec/models/merge_request_diff_spec.rb47
-rw-r--r--spec/models/merge_request_spec.rb94
-rw-r--r--spec/models/milestone_spec.rb10
-rw-r--r--spec/models/namespace_spec.rb19
-rw-r--r--spec/models/note_spec.rb18
-rw-r--r--spec/models/notification_setting_spec.rb17
-rw-r--r--spec/models/project_security_spec.rb2
-rw-r--r--spec/models/project_services/bugzilla_service_spec.rb49
-rw-r--r--spec/models/project_services/buildkite_service_spec.rb6
-rw-r--r--spec/models/project_services/builds_email_service_spec.rb38
-rw-r--r--spec/models/project_services/jira_service_spec.rb2
-rw-r--r--spec/models/project_services/slack_service/wiki_page_message_spec.rb1
-rw-r--r--spec/models/project_spec.rb227
-rw-r--r--spec/models/protected_branch_spec.rb125
-rw-r--r--spec/models/repository_spec.rb98
-rw-r--r--spec/models/service_spec.rb8
-rw-r--r--spec/models/snippet_spec.rb42
-rw-r--r--spec/models/user_spec.rb78
-rw-r--r--spec/requests/api/api_helpers_spec.rb28
-rw-r--r--spec/requests/api/award_emoji_spec.rb71
-rw-r--r--spec/requests/api/branches_spec.rb129
-rw-r--r--spec/requests/api/builds_spec.rb108
-rw-r--r--spec/requests/api/commit_statuses_spec.rb29
-rw-r--r--spec/requests/api/commits_spec.rb38
-rw-r--r--spec/requests/api/doorkeeper_access_spec.rb7
-rw-r--r--spec/requests/api/files_spec.rb24
-rw-r--r--spec/requests/api/fork_spec.rb12
-rw-r--r--spec/requests/api/gitignores_spec.rb29
-rw-r--r--spec/requests/api/group_members_spec.rb36
-rw-r--r--spec/requests/api/groups_spec.rb95
-rw-r--r--spec/requests/api/internal_spec.rb121
-rw-r--r--spec/requests/api/issues_spec.rb341
-rw-r--r--spec/requests/api/keys_spec.rb6
-rw-r--r--spec/requests/api/labels_spec.rb67
-rw-r--r--spec/requests/api/license_templates_spec.rb (renamed from spec/requests/api/licenses_spec.rb)8
-rw-r--r--spec/requests/api/merge_requests_spec.rb156
-rw-r--r--spec/requests/api/milestones_spec.rb36
-rw-r--r--spec/requests/api/namespaces_spec.rb10
-rw-r--r--spec/requests/api/notes_spec.rb85
-rw-r--r--spec/requests/api/project_hooks_spec.rb36
-rw-r--r--spec/requests/api/project_members_spec.rb36
-rw-r--r--spec/requests/api/project_snippets_spec.rb12
-rw-r--r--spec/requests/api/projects_spec.rb229
-rw-r--r--spec/requests/api/repositories_spec.rb40
-rw-r--r--spec/requests/api/runners_spec.rb118
-rw-r--r--spec/requests/api/services_spec.rb13
-rw-r--r--spec/requests/api/session_spec.rb8
-rw-r--r--spec/requests/api/settings_spec.rb14
-rw-r--r--spec/requests/api/sidekiq_metrics_spec.rb8
-rw-r--r--spec/requests/api/system_hooks_spec.rb14
-rw-r--r--spec/requests/api/tags_spec.rb40
-rw-r--r--spec/requests/api/templates_spec.rb52
-rw-r--r--spec/requests/api/todos_spec.rb189
-rw-r--r--spec/requests/api/triggers_spec.rb44
-rw-r--r--spec/requests/api/users_spec.rb203
-rw-r--r--spec/requests/api/variables_spec.rb38
-rw-r--r--spec/requests/ci/api/builds_spec.rb112
-rw-r--r--spec/requests/ci/api/triggers_spec.rb16
-rw-r--r--spec/requests/git_http_spec.rb75
-rw-r--r--spec/requests/jwt_controller_spec.rb10
-rw-r--r--spec/requests/lfs_http_spec.rb768
-rw-r--r--spec/routing/admin_routing_spec.rb1
-rw-r--r--spec/routing/project_routing_spec.rb1
-rw-r--r--spec/routing/routing_spec.rb20
-rw-r--r--spec/services/auth/container_registry_authentication_service_spec.rb104
-rw-r--r--spec/services/ci/create_trigger_request_service_spec.rb2
-rw-r--r--spec/services/ci/image_for_build_service_spec.rb2
-rw-r--r--spec/services/ci/register_build_service_spec.rb2
-rw-r--r--spec/services/create_commit_builds_service_spec.rb53
-rw-r--r--spec/services/create_deployment_service_spec.rb16
-rw-r--r--spec/services/create_tag_service_spec.rb4
-rw-r--r--spec/services/destroy_group_service_spec.rb8
-rw-r--r--spec/services/event_create_service_spec.rb20
-rw-r--r--spec/services/git_hooks_service_spec.rb12
-rw-r--r--spec/services/git_push_service_spec.rb58
-rw-r--r--spec/services/git_tag_push_service_spec.rb25
-rw-r--r--spec/services/issues/bulk_update_service_spec.rb38
-rw-r--r--spec/services/issues/close_service_spec.rb2
-rw-r--r--spec/services/members/destroy_service_spec.rb71
-rw-r--r--spec/services/merge_requests/close_service_spec.rb2
-rw-r--r--spec/services/merge_requests/create_service_spec.rb2
-rw-r--r--spec/services/merge_requests/merge_service_spec.rb12
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb6
-rw-r--r--spec/services/merge_requests/reopen_service_spec.rb2
-rw-r--r--spec/services/milestones/close_service_spec.rb2
-rw-r--r--spec/services/milestones/create_service_spec.rb2
-rw-r--r--spec/services/notes/create_service_spec.rb2
-rw-r--r--spec/services/notes/diff_position_update_service_spec.rb175
-rw-r--r--spec/services/notes/post_process_service_spec.rb2
-rw-r--r--spec/services/notification_service_spec.rb31
-rw-r--r--spec/services/projects/housekeeping_service_spec.rb44
-rw-r--r--spec/services/projects/import_service_spec.rb12
-rw-r--r--spec/services/projects/transfer_service_spec.rb1
-rw-r--r--spec/services/search/snippet_service_spec.rb59
-rw-r--r--spec/services/system_note_service_spec.rb2
-rw-r--r--spec/services/test_hook_service_spec.rb2
-rw-r--r--spec/services/todo_service_spec.rb53
-rw-r--r--spec/spec_helper.rb6
-rw-r--r--spec/support/capybara_helpers.rb8
-rw-r--r--spec/support/fake_u2f_device.rb4
-rw-r--r--spec/support/jira_service_helper.rb1
-rw-r--r--spec/support/login_helpers.rb37
-rw-r--r--spec/support/omni_auth.rb1
-rw-r--r--spec/support/relative_url.rb8
-rw-r--r--spec/support/test_env.rb38
-rw-r--r--spec/tasks/gitlab/backup_rake_spec.rb131
-rw-r--r--spec/views/projects/builds/show.html.haml_spec.rb52
-rw-r--r--spec/workers/git_garbage_collect_worker_spec.rb27
-rw-r--r--spec/workers/merge_worker_spec.rb2
-rw-r--r--spec/workers/post_receive_spec.rb2
-rw-r--r--spec/workers/project_cache_worker_spec.rb1
-rw-r--r--spec/workers/repository_fork_worker_spec.rb9
-rw-r--r--vendor/gitignore/Android.gitignore4
-rw-r--r--vendor/gitignore/C++.gitignore4
-rw-r--r--vendor/gitignore/C.gitignore3
-rw-r--r--vendor/gitignore/CMake.gitignore1
-rw-r--r--vendor/gitignore/D.gitignore4
-rw-r--r--vendor/gitignore/Global/Bazaar.gitignore2
-rw-r--r--vendor/gitignore/Global/OSX.gitignore3
-rw-r--r--vendor/gitignore/Global/README.md10
-rw-r--r--vendor/gitignore/Global/SublimeText.gitignore13
-rw-r--r--vendor/gitignore/Gradle.gitignore2
-rw-r--r--vendor/gitignore/Haskell.gitignore1
-rw-r--r--vendor/gitignore/Julia.gitignore4
-rw-r--r--vendor/gitignore/LICENSE121
-rw-r--r--vendor/gitignore/Laravel.gitignore1
-rw-r--r--vendor/gitignore/Node.gitignore1
-rw-r--r--vendor/gitignore/Objective-C.gitignore9
-rw-r--r--vendor/gitignore/Qt.gitignore2
-rw-r--r--vendor/gitignore/README.md14
-rw-r--r--vendor/gitignore/Rails.gitignore4
-rw-r--r--vendor/gitignore/Swift.gitignore2
-rw-r--r--vendor/gitignore/TeX.gitignore3
-rw-r--r--vendor/gitignore/UnrealEngine.gitignore1
-rw-r--r--vendor/gitignore/VisualStudio.gitignore1
-rw-r--r--vendor/gitlab-ci-yml/Docker.gitlab-ci.yml7
-rw-r--r--vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml18
-rw-r--r--vendor/gitlab-ci-yml/LICENSE21
-rw-r--r--vendor/gitlab-ci-yml/Maven.gitlab-ci.yml102
-rw-r--r--vendor/gitlab-ci-yml/Nodejs.gitlab-ci.yml27
-rw-r--r--vendor/gitlab-ci-yml/Pages/Brunch.gitlab-ci.yml16
-rw-r--r--vendor/gitlab-ci-yml/Pages/Doxygen.gitlab-ci.yml13
-rw-r--r--vendor/gitlab-ci-yml/Pages/HTML.gitlab-ci.yml12
-rw-r--r--vendor/gitlab-ci-yml/Pages/Harp.gitlab-ci.yml16
-rw-r--r--vendor/gitlab-ci-yml/Pages/Hexo.gitlab-ci.yml25
-rw-r--r--vendor/gitlab-ci-yml/Pages/Hugo.gitlab-ci.yml11
-rw-r--r--vendor/gitlab-ci-yml/Pages/Hyde.gitlab-ci.yml25
-rw-r--r--vendor/gitlab-ci-yml/Pages/Jekyll.gitlab-ci.yml24
-rw-r--r--vendor/gitlab-ci-yml/Pages/Lektor.gitlab-ci.yml12
-rw-r--r--vendor/gitlab-ci-yml/Pages/Metalsmith.gitlab-ci.yml17
-rw-r--r--vendor/gitlab-ci-yml/Pages/Middleman.gitlab-ci.yml27
-rw-r--r--vendor/gitlab-ci-yml/Pages/Nanoc.gitlab-ci.yml12
-rw-r--r--vendor/gitlab-ci-yml/Pages/Octopress.gitlab-ci.yml15
-rw-r--r--vendor/gitlab-ci-yml/Pages/Pelican.gitlab-ci.yml10
-rw-r--r--vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml37
-rw-r--r--vendor/gitlab-ci-yml/Rust.gitlab-ci.yml23
-rw-r--r--vendor/gitlab-ci-yml/Scala.gitlab-ci.yml22
1608 files changed, 46404 insertions, 21491 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 219077d79b8..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
@@ -134,6 +135,11 @@ spinach 9 10: *spinach-knapsack
image: "ruby:2.3"
only:
- master
+ cache:
+ key: "ruby-23"
+ paths:
+ - vendor/apt
+ - vendor/ruby
.rspec-knapsack-ruby23: &rspec-knapsack-ruby23
<<: *rspec-knapsack
diff --git a/.hound.yml b/.hound.yml
deleted file mode 100644
index 3bde29fb2bf..00000000000
--- a/.hound.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-# Prefer single quotes
-StringLiterals:
- EnforcedStyle: single_quotes
- Enabled: true
diff --git a/.rubocop.yml b/.rubocop.yml
index dbdabbb9d4c..db0bcfadcf4 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -1,4 +1,8 @@
-require: rubocop-rspec
+require:
+ - rubocop-rspec
+ - ./rubocop/rubocop
+
+inherit_from: .rubocop_todo.yml
AllCops:
TargetRubyVersion: 2.1
@@ -50,14 +54,6 @@ Style/AlignArray:
Style/AlignHash:
Enabled: true
-# Align the parameters of a method call if they span more than one line.
-Style/AlignParameters:
- Enabled: false
-
-# Use &&/|| instead of and/or.
-Style/AndOr:
- Enabled: false
-
# Use `Array#join` instead of `Array#*`.
Style/ArrayJoin:
Enabled: true
@@ -78,10 +74,6 @@ Style/Attr:
Style/BeginBlock:
Enabled: true
-# Checks if usage of %() or %Q() matches configuration.
-Style/BarePercentLiterals:
- Enabled: false
-
# Do not use block comments.
Style/BlockComments:
Enabled: true
@@ -95,14 +87,6 @@ Style/BlockEndNewline:
Style/BlockDelimiters:
Enabled: true
-# Enforce braces style around hash parameters.
-Style/BracesAroundHashParameters:
- Enabled: false
-
-# Avoid explicit use of the case equality operator(===).
-Style/CaseEquality:
- Enabled: false
-
# Indentation of when in a case/when/[else/]end.
Style/CaseIndentation:
Enabled: true
@@ -131,24 +115,10 @@ Style/ClassMethods:
Style/ClassVars:
Enabled: true
-# Do not use :: for method call.
-Style/ColonMethodCall:
- Enabled: false
-
-# Checks formatting of special comments (TODO, FIXME, OPTIMIZE, HACK, REVIEW).
-Style/CommentAnnotation:
- Enabled: false
-
# Indentation of comments.
Style/CommentIndentation:
Enabled: true
-# Use the return value of `if` and `case` statements for assignment to a
-# variable and variable comparison instead of assigning that variable
-# inside of each branch.
-Style/ConditionalAssignment:
- Enabled: false
-
# Constants should use SCREAMING_SNAKE_CASE.
Style/ConstantName:
Enabled: true
@@ -157,41 +127,21 @@ Style/ConstantName:
Style/DefWithParentheses:
Enabled: true
-# Checks for use of deprecated Hash methods.
-Style/DeprecatedHashMethods:
- Enabled: false
-
# Document classes and non-namespace modules.
Style/Documentation:
Enabled: false
-# Checks the position of the dot in multi-line method calls.
-Style/DotPosition:
- Enabled: false
-
-# Checks for uses of double negation (!!).
-Style/DoubleNegation:
- Enabled: false
-
-# Prefer `each_with_object` over `inject` or `reduce`.
-Style/EachWithObject:
- Enabled: false
-
# Align elses and elsifs correctly.
Style/ElseAlignment:
Enabled: true
-# Avoid empty else-clauses.
-Style/EmptyElse:
- Enabled: false
-
# Use empty lines between defs.
Style/EmptyLineBetweenDefs:
Enabled: false
# Don't use several empty lines in a row.
Style/EmptyLines:
- Enabled: false
+ Enabled: true
# Keep blank lines around access modifiers.
Style/EmptyLinesAroundAccessModifier:
@@ -213,10 +163,6 @@ Style/EmptyLinesAroundModuleBody:
Style/EmptyLinesAroundMethodBody:
Enabled: false
-# Prefer literals to Array.new/Hash.new/String.new.
-Style/EmptyLiteral:
- Enabled: false
-
# Avoid the use of END blocks.
Style/EndBlock:
Enabled: true
@@ -229,10 +175,6 @@ Style/EndOfLine:
Style/EvenOdd:
Enabled: true
-# Do not use unnecessary spacing.
-Style/ExtraSpacing:
- Enabled: false
-
# Use snake_case for source file names.
Style/FileName:
Enabled: true
@@ -250,31 +192,15 @@ Style/FlipFlop:
Style/For:
Enabled: true
-# Enforce the use of Kernel#sprintf, Kernel#format or String#%.
-Style/FormatString:
- Enabled: false
-
# Do not introduce global variables.
Style/GlobalVars:
Enabled: true
-# Check for conditionals that can be replaced with guard clauses.
-Style/GuardClause:
- Enabled: false
-
# Prefer Ruby 1.9 hash syntax `{ a: 1, b: 2 }`
# over 1.8 syntax `{ :a => 1, :b => 2 }`.
Style/HashSyntax:
Enabled: true
-# Finds if nodes inside else, which can be converted to elsif.
-Style/IfInsideElse:
- Enabled: false
-
-# Favor modifier if/unless usage when you have a single-line body.
-Style/IfUnlessModifier:
- Enabled: false
-
# Do not use if x; .... Use the ternary operator instead.
Style/IfWithSemicolon:
Enabled: true
@@ -282,7 +208,7 @@ Style/IfWithSemicolon:
# Checks that conditional statements do not have an identical line at the
# end of each branch, which can validly be moved out of the conditional.
Style/IdenticalConditionalBranches:
- Enabled: false
+ Enabled: true
# Checks the indentation of the first line of the right-hand-side of a
# multi-line assignment.
@@ -297,22 +223,10 @@ Style/IndentationConsistency:
Style/IndentationWidth:
Enabled: true
-# Checks the indentation of the first element in an array literal.
-Style/IndentArray:
- Enabled: false
-
-# Checks the indentation of the first key in a hash literal.
-Style/IndentHash:
- Enabled: false
-
# Use Kernel#loop for infinite loops.
Style/InfiniteLoop:
Enabled: true
-# Use the new lambda literal syntax for single-line blocks.
-Style/Lambda:
- Enabled: false
-
# Use lambda.call(...) instead of lambda.(...).
Style/LambdaCall:
Enabled: true
@@ -321,14 +235,6 @@ Style/LambdaCall:
Style/LeadingCommentSpace:
Enabled: true
-# Use \ instead of + or << to concatenate two string literals at line end.
-Style/LineEndConcatenation:
- Enabled: false
-
-# Do not use parentheses for method calls with no arguments.
-Style/MethodCallParentheses:
- Enabled: false
-
# Checks if the method definitions have or don't have parentheses.
Style/MethodDefParentheses:
Enabled: true
@@ -385,39 +291,18 @@ Style/MultilineMethodDefinitionBraceLayout:
Style/MultilineOperationIndentation:
Enabled: false
-# Avoid multi-line `? :` (the ternary operator), use if/unless instead.
-Style/MultilineTernaryOperator:
- Enabled: false
-
-# Do not assign mutable objects to constants.
-Style/MutableConstant:
- Enabled: false
-
# Favor unless over if for negative conditions (or control flow or).
Style/NegatedIf:
Enabled: true
-# Favor until over while for negative conditions.
-Style/NegatedWhile:
- Enabled: false
-
# Avoid using nested modifiers.
Style/NestedModifier:
Enabled: true
-# Parenthesize method calls which are nested inside the argument list of
-# another parenthesized method call.
-Style/NestedParenthesizedCalls:
- Enabled: false
-
# Use one expression per branch in a ternary operator.
Style/NestedTernaryOperator:
Enabled: true
-# Use `next` to skip iteration instead of a condition at the end.
-Style/Next:
- Enabled: false
-
# Prefer x.nil? to x == nil.
Style/NilComparison:
Enabled: true
@@ -442,51 +327,10 @@ Style/OneLineConditional:
Style/OpMethod:
Enabled: true
-# Check for simple usages of parallel assignment. It will only warn when
-# the number of variables matches on both sides of the assignment.
-Style/ParallelAssignment:
- Enabled: false
-
# Don't use parentheses around the condition of an if/unless/while.
Style/ParenthesesAroundCondition:
Enabled: true
-# Use `%`-literal delimiters consistently.
-Style/PercentLiteralDelimiters:
- Enabled: false
-
-# Checks if uses of %Q/%q match the configured preference.
-Style/PercentQLiterals:
- Enabled: false
-
-# Avoid Perl-style regex back references.
-Style/PerlBackrefs:
- Enabled: false
-
-# Check the names of predicate methods.
-Style/PredicateName:
- Enabled: false
-
-# Use proc instead of Proc.new.
-Style/Proc:
- Enabled: false
-
-# Checks the arguments passed to raise/fail.
-Style/RaiseArgs:
- Enabled: false
-
-# Don't use begin blocks when they are not needed.
-Style/RedundantBegin:
- Enabled: false
-
-# Checks for an obsolete RuntimeException argument in raise/fail.
-Style/RedundantException:
- Enabled: false
-
-# Checks usages of Object#freeze on immutable objects.
-Style/RedundantFreeze:
- Enabled: false
-
# Checks for parentheses that seem not to serve any purpose.
Style/RedundantParentheses:
Enabled: true
@@ -495,24 +339,6 @@ Style/RedundantParentheses:
Style/RedundantReturn:
Enabled: true
-# Don't use self where it's not needed.
-Style/RedundantSelf:
- Enabled: false
-
-# Use %r for regular expressions matching more than `MaxSlashes` '/'
-# characters. Use %r only for regular expressions matching more
-# than `MaxSlashes` '/' character.
-Style/RegexpLiteral:
- Enabled: false
-
-# Avoid using rescue in its modifier form.
-Style/RescueModifier:
- Enabled: false
-
-# Checks for places where self-assignment shorthand should have been used.
-Style/SelfAssignment:
- Enabled: false
-
# Don't use semicolons to terminate expressions.
Style/Semicolon:
Enabled: true
@@ -522,21 +348,13 @@ Style/SignalException:
EnforcedStyle: only_raise
Enabled: true
-# Enforces the names of some block params.
-Style/SingleLineBlockParams:
- Enabled: false
-
-# Avoid single-line methods.
-Style/SingleLineMethods:
- Enabled: false
-
# Use spaces after colons.
Style/SpaceAfterColon:
- Enabled: false
+ Enabled: true
# Use spaces after commas.
Style/SpaceAfterComma:
- Enabled: false
+ Enabled: true
# Do not put a space between a method name and the opening parenthesis in a
# method definition.
@@ -551,11 +369,6 @@ Style/SpaceAfterNot:
Style/SpaceAfterSemicolon:
Enabled: true
-# Checks that the equals signs in parameter default assignments have or don't
-# have surrounding space depending on configuration.
-Style/SpaceAroundEqualsInParameterDefault:
- Enabled: false
-
# Use a space around keywords if appropriate.
Style/SpaceAroundKeyword:
Enabled: true
@@ -564,10 +377,6 @@ Style/SpaceAroundKeyword:
Style/SpaceAroundOperators:
Enabled: true
-# Checks that the left block brace has or doesn't have space before it.
-Style/SpaceBeforeBlockBraces:
- Enabled: false
-
# No spaces before commas.
Style/SpaceBeforeComma:
Enabled: true
@@ -576,33 +385,14 @@ Style/SpaceBeforeComma:
Style/SpaceBeforeComment:
Enabled: true
-# Checks that exactly one space is used between a method name and the first
-# argument for method calls without parentheses.
-Style/SpaceBeforeFirstArg:
- Enabled: false
-
# No spaces before semicolons.
Style/SpaceBeforeSemicolon:
Enabled: true
-# Checks that block braces have or don't have surrounding space.
-# For blocks taking parameters, checks that the left brace has or doesn't
-# have trailing space.
-Style/SpaceInsideBlockBraces:
- Enabled: false
-
-# No spaces after [ or before ].
-Style/SpaceInsideBrackets:
- Enabled: false
-
# Use spaces inside hash literal braces - or don't.
Style/SpaceInsideHashLiteralBraces:
Enabled: true
-# No spaces after ( or before ).
-Style/SpaceInsideParens:
- Enabled: false
-
# No spaces inside range literals.
Style/SpaceInsideRangeLiteral:
Enabled: true
@@ -612,10 +402,6 @@ Style/SpaceInsideStringInterpolation:
EnforcedStyle: no_space
Enabled: true
-# Avoid Perl-style global variables.
-Style/SpecialGlobalVars:
- Enabled: false
-
# Check for the usage of parentheses around stabby lambda arguments.
Style/StabbyLambdaParentheses:
EnforcedStyle: require_parentheses
@@ -625,25 +411,12 @@ Style/StabbyLambdaParentheses:
Style/StringLiterals:
Enabled: false
-# Checks if uses of quotes inside expressions in interpolated strings match the
-# configured preference.
-Style/StringLiteralsInInterpolation:
- Enabled: false
-
# Checks if configured preferred methods are used over non-preferred.
Style/StringMethods:
PreferredMethods:
intern: to_sym
Enabled: true
-# Use %i or %I for arrays of symbols.
-Style/SymbolArray:
- Enabled: false
-
-# Use symbols as procs instead of blocks when possible.
-Style/SymbolProc:
- Enabled: false
-
# No hard tabs.
Style/Tab:
Enabled: true
@@ -652,39 +425,9 @@ Style/Tab:
Style/TrailingBlankLines:
Enabled: true
-# Checks for trailing comma in array and hash literals.
-Style/TrailingCommaInLiteral:
- Enabled: false
-
-# Checks for trailing comma in argument lists.
-Style/TrailingCommaInArguments:
- Enabled: false
-
-# Avoid trailing whitespace.
-Style/TrailingWhitespace:
- Enabled: false
-
-# Checks for the usage of unneeded trailing underscores at the end of
-# parallel variable assignment.
-Style/TrailingUnderscoreVariable:
- Enabled: false
-
-# Prefer attr_* methods to trivial readers/writers.
-Style/TrivialAccessors:
- Enabled: false
-
-# Do not use unless with else. Rewrite these with the positive case first.
-Style/UnlessElse:
- Enabled: false
-
# Checks for %W when interpolation is not needed.
Style/UnneededCapitalW:
- Enabled: false
-
-# TODO: Enable UnneededInterpolation Cop.
-# Checks for strings that are just an interpolated expression.
-Style/UnneededInterpolation:
- Enabled: false
+ Enabled: true
# Checks for %q/%Q when single quotes or double quotes would do.
Style/UnneededPercentQ:
@@ -715,12 +458,6 @@ Style/WhileUntilModifier:
Style/WordArray:
Enabled: false
-# TODO: Enable ZeroLengthPredicate Cop.
-# Use #empty? when testing for objects of length 0.
-Style/ZeroLengthPredicate:
- Enabled: false
-
-
#################### Metrics ################################
# A calculated magnitude based on number of assignments,
@@ -774,15 +511,6 @@ Metrics/PerceivedComplexity:
Lint/AmbiguousOperator:
Enabled: true
-# Checks for ambiguous regexp literals in the first argument of a method
-# invocation without parentheses.
-Lint/AmbiguousRegexpLiteral:
- Enabled: false
-
-# Don't use assignment in conditions.
-Lint/AssignmentInCondition:
- Enabled: false
-
# Align block ends correctly.
Lint/BlockAlignment:
Enabled: true
@@ -808,14 +536,6 @@ Lint/DefEndAlignment:
Lint/DeprecatedClassMethods:
Enabled: true
-# Check for duplicate method definitions.
-Lint/DuplicateMethods:
- Enabled: false
-
-# Check for duplicate keys in hash literals.
-Lint/DuplicatedKey:
- Enabled: false
-
# Check for immutable argument given to each_with_object.
Lint/EachWithObjectArgument:
Enabled: true
@@ -828,10 +548,6 @@ Lint/ElseLayout:
Lint/EmptyEnsure:
Enabled: true
-# Checks for empty string interpolation.
-Lint/EmptyInterpolation:
- Enabled: false
-
# Align ends correctly.
Lint/EndAlignment:
Enabled: true
@@ -856,21 +572,11 @@ Lint/FloatOutOfRange:
Lint/FormatParameterMismatch:
Enabled: true
-# Don't suppress exception.
-Lint/HandleExceptions:
- Enabled: false
-
# Checks for adjacent string literals on the same line, which could better be
# represented as a single string literal.
Lint/ImplicitStringConcatenation:
Enabled: true
-# TODO: Enable IneffectiveAccessModifier Cop.
-# Checks for attempts to use `private` or `protected` to set the visibility
-# of a class method, which does not work.
-Lint/IneffectiveAccessModifier:
- Enabled: false
-
# Checks for invalid character literals with a non-escaped whitespace
# character.
Lint/InvalidCharacterLiteral:
@@ -884,11 +590,6 @@ Lint/LiteralInCondition:
Lint/LiteralInInterpolation:
Enabled: true
-# Use Kernel#loop with break rather than begin/end/until or begin/end/while
-# for post-loop tests.
-Lint/Loop:
- Enabled: false
-
# Do not use nested method definitions.
Lint/NestedMethodDefinition:
Enabled: true
@@ -914,13 +615,8 @@ Lint/RequireParentheses:
Lint/RescueException:
Enabled: true
-# Do not use the same name as outer local variable for block arguments
-# or block local variables.
-Lint/ShadowingOuterLocalVariable:
- Enabled: false
-
-# 'Checks for Object#to_s usage in string interpolation.
-Lint/StringConversionInInterpolation:
+# Checks for the order which exceptions are rescued to avoid rescueing a less specific exception before a more specific exception.
+Lint/ShadowedException:
Enabled: false
# Do not use prefix `_` for a variable that is used.
@@ -933,22 +629,10 @@ Lint/UnderscorePrefixedVariableName:
Lint/UnneededDisable:
Enabled: false
-# Checks for unused block arguments.
-Lint/UnusedBlockArgument:
- Enabled: false
-
-# Checks for unused method arguments.
-Lint/UnusedMethodArgument:
- Enabled: false
-
# Unreachable code.
Lint/UnreachableCode:
Enabled: true
-# Checks for useless access modifiers.
-Lint/UselessAccessModifier:
- Enabled: false
-
# Checks for useless assignment to a local variable.
Lint/UselessAssignment:
Enabled: true
@@ -981,11 +665,6 @@ Performance/Casecmp:
Performance/DoubleStartEndWith:
Enabled: true
-# TODO: Enable EndWith Cop.
-# Use `end_with?` instead of a regex match anchored to the end of a string.
-Performance/EndWith:
- Enabled: false
-
# Use `strip` instead of `lstrip.rstrip`.
Performance/LstripRstrip:
Enabled: true
@@ -994,24 +673,6 @@ Performance/LstripRstrip:
Performance/RangeInclude:
Enabled: true
-# TODO: Enable RedundantBlockCall Cop.
-# Use `yield` instead of `block.call`.
-Performance/RedundantBlockCall:
- Enabled: false
-
-# TODO: Enable RedundantMatch Cop.
-# Use `=~` instead of `String#match` or `Regexp#match` in a context where the
-# returned `MatchData` is not needed.
-Performance/RedundantMatch:
- Enabled: false
-
-# TODO: Enable RedundantMerge Cop.
-# Use `Hash#[]=`, rather than `Hash#merge!` with a single key-value pair.
-Performance/RedundantMerge:
- # Max number of key-value pairs to consider an offense
- MaxKeyValuePairs: 2
- Enabled: false
-
# Use `sort` instead of `sort_by { |x| x }`.
Performance/RedundantSortBy:
Enabled: true
@@ -1080,18 +741,6 @@ Rails/ReadWriteAttribute:
Rails/ScopeArgs:
Enabled: true
-# Checks the correct usage of time zone aware methods.
-# http://danilenko.org/2012/7/6/rails_timezones
-Rails/TimeZone:
- Enabled: false
-
-# Use validates :attribute, hash of validations.
-Rails/Validation:
- Enabled: false
-
-Rails/UniqBeforePluck:
- Enabled: false
-
##################### RSpec ##################################
# Check that instances are not being stubbed globally.
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
new file mode 100644
index 00000000000..9310e711889
--- /dev/null
+++ b/.rubocop_todo.yml
@@ -0,0 +1,462 @@
+# This configuration was generated by
+# `rubocop --auto-gen-config --exclude-limit 0`
+# on 2016-07-13 12:36:08 -0600 using RuboCop version 0.41.2.
+# The point is for the user to remove these configuration records
+# one by one as the offenses are removed from the code base.
+# Note that changes in the inspected code, or installation of new
+# versions of RuboCop, may require this file to be generated again.
+
+# Offense count: 154
+Lint/AmbiguousRegexpLiteral:
+ Enabled: false
+
+# Offense count: 43
+# Configuration parameters: AllowSafeAssignment.
+Lint/AssignmentInCondition:
+ Enabled: false
+
+# Offense count: 14
+Lint/HandleExceptions:
+ Enabled: false
+
+# Offense count: 21
+Lint/IneffectiveAccessModifier:
+ Enabled: false
+
+# Offense count: 2
+Lint/Loop:
+ Enabled: false
+
+# Offense count: 15
+Lint/ShadowingOuterLocalVariable:
+ Enabled: false
+
+# Offense count: 3
+# Cop supports --auto-correct.
+Lint/StringConversionInInterpolation:
+ Enabled: false
+
+# Offense count: 44
+# Cop supports --auto-correct.
+# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments.
+Lint/UnusedBlockArgument:
+ Enabled: false
+
+# Offense count: 129
+# Cop supports --auto-correct.
+# Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods.
+Lint/UnusedMethodArgument:
+ Enabled: false
+
+# Offense count: 11
+Lint/UselessAccessModifier:
+ Enabled: false
+
+# Offense count: 12
+# Cop supports --auto-correct.
+Performance/PushSplat:
+ Enabled: false
+
+# Offense count: 2
+# Cop supports --auto-correct.
+Performance/RedundantBlockCall:
+ Enabled: false
+
+# Offense count: 4
+# Cop supports --auto-correct.
+Performance/RedundantMatch:
+ Enabled: false
+
+# Offense count: 24
+# Cop supports --auto-correct.
+# Configuration parameters: MaxKeyValuePairs.
+Performance/RedundantMerge:
+ Enabled: false
+
+# Offense count: 60
+Rails/OutputSafety:
+ Enabled: false
+
+# Offense count: 128
+# Configuration parameters: EnforcedStyle, SupportedStyles.
+# SupportedStyles: strict, flexible
+Rails/TimeZone:
+ Enabled: false
+
+# Offense count: 12
+# Cop supports --auto-correct.
+# Configuration parameters: Include.
+# Include: app/models/**/*.rb
+Rails/Validation:
+ Enabled: false
+
+# Offense count: 217
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth.
+# SupportedStyles: with_first_parameter, with_fixed_indentation
+Style/AlignParameters:
+ Enabled: false
+
+# Offense count: 32
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle, SupportedStyles.
+# SupportedStyles: always, conditionals
+Style/AndOr:
+ Enabled: false
+
+# Offense count: 47
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle, SupportedStyles.
+# SupportedStyles: percent_q, bare_percent
+Style/BarePercentLiterals:
+ Enabled: false
+
+# Offense count: 258
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle, SupportedStyles.
+# SupportedStyles: braces, no_braces, context_dependent
+Style/BracesAroundHashParameters:
+ Enabled: false
+
+# Offense count: 5
+Style/CaseEquality:
+ Enabled: false
+
+# Offense count: 19
+# Cop supports --auto-correct.
+Style/ColonMethodCall:
+ Enabled: false
+
+# Offense count: 3
+# Cop supports --auto-correct.
+# Configuration parameters: Keywords.
+# Keywords: TODO, FIXME, OPTIMIZE, HACK, REVIEW
+Style/CommentAnnotation:
+ Enabled: false
+
+# Offense count: 34
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle, SupportedStyles, SingleLineConditionsOnly.
+# SupportedStyles: assign_to_condition, assign_inside_condition
+Style/ConditionalAssignment:
+ Enabled: false
+
+# Offense count: 789
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle, SupportedStyles.
+# SupportedStyles: leading, trailing
+Style/DotPosition:
+ Enabled: false
+
+# Offense count: 13
+Style/DoubleNegation:
+ Enabled: false
+
+# Offense count: 3
+Style/EachWithObject:
+ Enabled: false
+
+# Offense count: 30
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle, SupportedStyles.
+# SupportedStyles: empty, nil, both
+Style/EmptyElse:
+ Enabled: false
+
+# Offense count: 3
+# Cop supports --auto-correct.
+Style/EmptyLiteral:
+ Enabled: false
+
+# Offense count: 123
+# Cop supports --auto-correct.
+# Configuration parameters: AllowForAlignment, ForceEqualSignAlignment.
+Style/ExtraSpacing:
+ Enabled: false
+
+# Offense count: 7
+# Configuration parameters: EnforcedStyle, SupportedStyles.
+# SupportedStyles: format, sprintf, percent
+Style/FormatString:
+ Enabled: false
+
+# Offense count: 48
+# Configuration parameters: MinBodyLength.
+Style/GuardClause:
+ Enabled: false
+
+# Offense count: 11
+Style/IfInsideElse:
+ Enabled: false
+
+# Offense count: 177
+# Cop supports --auto-correct.
+# Configuration parameters: MaxLineLength.
+Style/IfUnlessModifier:
+ Enabled: false
+
+# Offense count: 52
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth.
+# SupportedStyles: special_inside_parentheses, consistent, align_brackets
+Style/IndentArray:
+ Enabled: false
+
+# Offense count: 89
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth.
+# SupportedStyles: special_inside_parentheses, consistent, align_braces
+Style/IndentHash:
+ Enabled: false
+
+# Offense count: 12
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle, SupportedStyles.
+# SupportedStyles: line_count_dependent, lambda, literal
+Style/Lambda:
+ Enabled: false
+
+# Offense count: 6
+# Cop supports --auto-correct.
+Style/LineEndConcatenation:
+ Enabled: false
+
+# Offense count: 13
+# Cop supports --auto-correct.
+Style/MethodCallParentheses:
+ Enabled: false
+
+# Offense count: 3
+Style/MultilineTernaryOperator:
+ Enabled: false
+
+# Offense count: 62
+# Cop supports --auto-correct.
+Style/MutableConstant:
+ Enabled: false
+
+# Offense count: 10
+# Cop supports --auto-correct.
+Style/NestedParenthesizedCalls:
+ Enabled: false
+
+# Offense count: 12
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle, MinBodyLength, SupportedStyles.
+# SupportedStyles: skip_modifier_ifs, always
+Style/Next:
+ Enabled: false
+
+# Offense count: 8
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedOctalStyle, SupportedOctalStyles.
+# SupportedOctalStyles: zero_with_o, zero_only
+Style/NumericLiteralPrefix:
+ Enabled: false
+
+# Offense count: 29
+# Cop supports --auto-correct.
+Style/ParallelAssignment:
+ Enabled: false
+
+# Offense count: 208
+# Cop supports --auto-correct.
+# Configuration parameters: PreferredDelimiters.
+Style/PercentLiteralDelimiters:
+ Enabled: false
+
+# Offense count: 11
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle, SupportedStyles.
+# SupportedStyles: lower_case_q, upper_case_q
+Style/PercentQLiterals:
+ Enabled: false
+
+# Offense count: 13
+# Cop supports --auto-correct.
+Style/PerlBackrefs:
+ Enabled: false
+
+# Offense count: 32
+# Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist.
+# NamePrefix: is_, has_, have_
+# NamePrefixBlacklist: is_, has_, have_
+# NameWhitelist: is_a?
+Style/PredicateName:
+ Enabled: false
+
+# Offense count: 28
+# Cop supports --auto-correct.
+Style/PreferredHashMethods:
+ Enabled: false
+
+# Offense count: 6
+# Cop supports --auto-correct.
+Style/Proc:
+ Enabled: false
+
+# Offense count: 20
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle, SupportedStyles.
+# SupportedStyles: compact, exploded
+Style/RaiseArgs:
+ Enabled: false
+
+# Offense count: 3
+# Cop supports --auto-correct.
+Style/RedundantBegin:
+ Enabled: false
+
+# Offense count: 1
+# Cop supports --auto-correct.
+Style/RedundantException:
+ Enabled: false
+
+# Offense count: 23
+# Cop supports --auto-correct.
+Style/RedundantFreeze:
+ Enabled: false
+
+# Offense count: 377
+# Cop supports --auto-correct.
+Style/RedundantSelf:
+ Enabled: false
+
+# Offense count: 94
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes.
+# SupportedStyles: slashes, percent_r, mixed
+Style/RegexpLiteral:
+ Enabled: false
+
+# Offense count: 17
+# Cop supports --auto-correct.
+Style/RescueModifier:
+ Enabled: false
+
+# Offense count: 2
+# Cop supports --auto-correct.
+Style/SelfAssignment:
+ Enabled: false
+
+# Offense count: 2
+# Configuration parameters: Methods.
+# Methods: {"reduce"=>["a", "e"]}, {"inject"=>["a", "e"]}
+Style/SingleLineBlockParams:
+ Enabled: false
+
+# Offense count: 50
+# Cop supports --auto-correct.
+# Configuration parameters: AllowIfMethodIsEmpty.
+Style/SingleLineMethods:
+ Enabled: false
+
+# Offense count: 14
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle, SupportedStyles.
+# SupportedStyles: space, no_space
+Style/SpaceAroundEqualsInParameterDefault:
+ Enabled: false
+
+# Offense count: 119
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle, SupportedStyles.
+# SupportedStyles: space, no_space
+Style/SpaceBeforeBlockBraces:
+ Enabled: false
+
+# Offense count: 11
+# Cop supports --auto-correct.
+# Configuration parameters: AllowForAlignment.
+Style/SpaceBeforeFirstArg:
+ Enabled: false
+
+# Offense count: 130
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters.
+# SupportedStyles: space, no_space
+Style/SpaceInsideBlockBraces:
+ Enabled: false
+
+# Offense count: 98
+# Cop supports --auto-correct.
+Style/SpaceInsideBrackets:
+ Enabled: false
+
+# Offense count: 60
+# Cop supports --auto-correct.
+Style/SpaceInsideParens:
+ Enabled: false
+
+# Offense count: 5
+# Cop supports --auto-correct.
+Style/SpaceInsidePercentLiteralDelimiters:
+ Enabled: false
+
+# Offense count: 36
+# Cop supports --auto-correct.
+# Configuration parameters: SupportedStyles.
+# SupportedStyles: use_perl_names, use_english_names
+Style/SpecialGlobalVars:
+ EnforcedStyle: use_perl_names
+
+# Offense count: 30
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyle, SupportedStyles.
+# SupportedStyles: single_quotes, double_quotes
+Style/StringLiteralsInInterpolation:
+ Enabled: false
+
+# Offense count: 24
+# Cop supports --auto-correct.
+# Configuration parameters: IgnoredMethods.
+# IgnoredMethods: respond_to, define_method
+Style/SymbolProc:
+ Enabled: false
+
+# Offense count: 23
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyleForMultiline, SupportedStyles.
+# SupportedStyles: comma, consistent_comma, no_comma
+Style/TrailingCommaInArguments:
+ Enabled: false
+
+# Offense count: 113
+# Cop supports --auto-correct.
+# Configuration parameters: EnforcedStyleForMultiline, SupportedStyles.
+# SupportedStyles: comma, consistent_comma, no_comma
+Style/TrailingCommaInLiteral:
+ Enabled: false
+
+# Offense count: 7
+# Cop supports --auto-correct.
+# Configuration parameters: AllowNamedUnderscoreVariables.
+Style/TrailingUnderscoreVariable:
+ Enabled: false
+
+# Offense count: 90
+# Cop supports --auto-correct.
+Style/TrailingWhitespace:
+ Enabled: false
+
+# Offense count: 2
+# Cop supports --auto-correct.
+# Configuration parameters: ExactNameMatch, AllowPredicates, AllowDSLWriters, IgnoreClassMethods, Whitelist.
+# Whitelist: to_ary, to_a, to_c, to_enum, to_h, to_hash, to_i, to_int, to_io, to_open, to_path, to_proc, to_r, to_regexp, to_str, to_s, to_sym
+Style/TrivialAccessors:
+ Enabled: false
+
+# Offense count: 3
+# Cop supports --auto-correct.
+Style/UnlessElse:
+ Enabled: false
+
+# Offense count: 13
+# Cop supports --auto-correct.
+Style/UnneededInterpolation:
+ Enabled: false
+
+# Offense count: 8
+# Cop supports --auto-correct.
+Style/ZeroLengthPredicate:
+ Enabled: false
diff --git a/.teatro.yml b/.teatro.yml
deleted file mode 100644
index 30054361981..00000000000
--- a/.teatro.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-stage:
- before:
- - cp config/gitlab.teatro.yml config/gitlab.yml
- - mkdir /apps/gitlab-satellites
- - mkdir /apps/repositories
-
- database:
- - RAILS_ENV=development force=yes bundle exec rake db:create gitlab:setup \ No newline at end of file
diff --git a/CHANGELOG b/CHANGELOG
index f651097828e..0dc7e5b114f 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,252 @@
Please view this file on the master branch, on stable branches it's out of date.
-
-v 8.9.0 (unreleased)
+v 8.11.0 (unreleased)
+ - Fix of 'Commits being passed to custom hooks are already reachable when using the UI'
+ - Limit git rev-list output count to one in forced push check
+
+v 8.10.0 (unreleased)
+ - Fix profile activity heatmap to show correct day name (eanplatter)
+ - Expose {should,force}_remove_source_branch (Ben Boeckel)
+ - Add the functionality to be able to rename a file. !5049 (tiagonbotelho)
+ - Disable PostgreSQL statement timeout during migrations
+ - Fix projects dropdown loading performance with a simplified api cal. !5113 (tiagonbotelho)
+ - Fix commit builds API, return all builds for all pipelines for given commit. !4849
+ - Replace Haml with Hamlit to make view rendering faster. !3666
+ - Refresh the branch cache after `git gc` runs
+ - Refactor repository paths handling to allow multiple git mount points
+ - Optimize system note visibility checking by memoizing the visible reference count !5070
+ - Add Application Setting to configure default Repository Path for new projects
+ - Delete award emoji when deleting a user
+ - Remove pinTo from Flash and make inline flash messages look nicer !4854 (winniehell)
+ - Wrap code blocks on Activies and Todos page. !4783 (winniehell)
+ - Align flash messages with left side of page content !4959 (winniehell)
+ - Display tooltip for "Copy to Clipboard" button !5164 (winniehell)
+ - Use default cursor for table header of project files !5165 (winniehell)
+ - Store when and yaml variables in builds table
+ - Display last commit of deleted branch in push events !4699 (winniehell)
+ - Escape file extension when parsing search results !5141 (winniehell)
+ - Apply the trusted_proxies config to the rack request object for use with rack_attack
+ - Upgrade to Rails 4.2.7. !5236
+ - Allow to pull code with deploy key from public projects
+ - Add Sidekiq queue duration to transaction metrics.
+ - Add a new column `artifacts_size` to table `ci_builds` !4964
+ - Let Workhorse serve format-patch diffs
+ - Display tooltip for mentioned users and groups !5261 (winniehell)
+ - Allow build email service to be tested
+ - Added day name to contribution calendar tooltips
+ - Make images fit to the size of the viewport !4810
+ - Fix check for New Branch button on Issue page !4630 (winniehell)
+ - Fix MR-auto-close text added to description. !4836
+ - Support U2F devices in Firefox. !5177
+ - Fix issue, preventing users w/o push access to sort tags !5105 (redetection)
+ - Add Spring EmojiOne updates.
+ - Added Rake task for tracking deployments !5320
+ - Fix fetching LFS objects for private CI projects
+ - Add the new 2016 Emoji! Adds 72 new emoji including bacon, facepalm, and selfie. !5237
+ - Add syntax for multiline blockquote using `>>>` fence !3954
+ - Fix viewing notification settings when a project is pending deletion
+ - Updated compare dropdown menus to use GL dropdown
+ - Redirects back to issue after clicking login link
+ - Eager load award emoji on notes
+ - Allow to define manual actions/builds on Pipelines and Environments
+ - Fix pagination when sorting by columns with lots of ties (like priority)
+ - The Markdown reference parsers now re-use query results to prevent running the same queries multiple times !5020
+ - Updated project header design
+ - Issuable collapsed assignee tooltip is now the users name
+ - Exclude email check from the standard health check
+ - Updated layout for Projects, Groups, Users on Admin area !4424
+ - Fix changing issue state columns in milestone view
+ - Update health_check gem to version 2.1.0
+ - Add notification settings dropdown for groups
+ - Render inline diffs for multiple changed lines following eachother
+ - Wildcards for protected branches. !4665
+ - Allow importing from Github using Personal Access Tokens. (Eric K Idema)
+ - API: Expose `due_date` for issues (Robert Schilling)
+ - API: Todos !3188 (Robert Schilling)
+ - API: Expose shared groups for projects and shared projects for groups !5050 (Robert Schilling)
+ - API: Expose `developers_can_push` and `developers_can_merge` for branches !5208 (Robert Schilling)
+ - Add "Enabled Git access protocols" to Application Settings
+ - Diffs will create button/diff form on demand no on server side
+ - Reduce size of HTML used by diff comment forms
+ - Protected branches have a "Developers can Merge" setting. !4892 (original implementation by Mathias Vestergaard)
+ - Fix user creation with stronger minimum password requirements !4054 (nathan-pmt)
+ - Only show New Snippet button to users that can create snippets.
+ - PipelinesFinder uses git cache data
+ - Track a user who created a pipeline
+ - Actually render old and new sections of parallel diff next to each other
+ - Throttle the update of `project.pushes_since_gc` to 1 minute.
+ - Allow expanding and collapsing files in diff view (!4990)
+ - Collapse large diffs by default (!4990)
+ - Fix mentioned users list on diff notes
+ - Fix creation of deployment on build that is retried, redeployed or rollback
+ - Don't parse Rinku returned value to DocFragment when it didn't change the original html string.
+ - Check for conflicts with existing Project's wiki path when creating a new project.
+ - Show last push widget in upstream after push to fork
+ - Fix stage status shown for pipelines
+ - Cache todos pending/done dashboard query counts.
+ - Don't instantiate a git tree on Projects show default view
+ - Bump Rinku to 2.0.0
+ - Remove unused front-end variable -> default_issues_tracker
+ - ObjectRenderer retrieve renderer content using Rails.cache.read_multi
+ - Better caching of git calls on ProjectsController#show.
+ - Avoid to retrieve MR closes_issues as much as possible.
+ - Add API endpoint for a group issues !4520 (mahcsig)
+ - Add Bugzilla integration !4930 (iamtjg)
+ - Instrument Rinku usage
+ - Be explicit to define merge request discussion variables
+ - Metrics for Rouge::Plugins::Redcarpet and Rouge::Formatters::HTMLGitlab
+ - RailsCache metris now includes fetch_hit/fetch_miss and read_hit/read_miss info.
+ - Allow [ci skip] to be in any case and allow [skip ci]. !4785 (simon_w)
+ - Set import_url validation to be more strict
+ - Memoize MR merged/closed events retrieval
+ - Don't render discussion notes when requesting diff tab through AJAX
+ - Add basic system information like memory and disk usage to the admin panel
+ - Don't garbage collect commits that have related DB records like comments
+ - More descriptive message for git hooks and file locks
+ - Aliases of award emoji should be stored as original name. !5060 (dixpac)
+ - Handle custom Git hook result in GitLab UI
+ - Allow to access Container Registry for Public and Internal projects
+ - Allow '?', or '&' for label names
+ - Support redirected blobs for Container Registry integration
+ - Fix importer for GitHub Pull Requests when a branch was reused across Pull Requests
+ - Add date when user joined the team on the member page
+ - Fix 404 redirect after validation fails importing a GitLab project
+ - Added setting to set new users by default as external !4545 (Dravere)
+ - Add min value for project limit field on user's form !3622 (jastkand)
+ - Reset project pushes_since_gc when we enqueue the git gc call
+ - Add reminder to not paste private SSH keys !4399 (Ingo Blechschmidt)
+ - Collapsed diffs lines/size don't acumulate to overflow diffs.
+ - Remove duplicate `description` field in `MergeRequest` entities (Ben Boeckel)
+ - Style of import project buttons were fixed in the new project page. !5183 (rdemirbay)
+ - Fix GitHub client requests when rate limit is disabled
+ - Optimistic locking for Issues and Merge Requests (Title and description overriding prevention)
+ - Redesign Builds and Pipelines pages
+ - Change status color and icon for running builds
+ - Fix markdown rendering for: consecutive labels references, label references that begin with a digit or contains `.`
+ - Project export filename now includes the project and namespace path
+ - Fix last update timestamp on issues not preserved on gitlab.com and project imports
+ - Fix issues importing projects from EE to CE
+ - Fix creating group with space in group path
+ - Improve cron_jobs loading error messages !5318
+ - Create Todos for Issue author when assign or mention himself (Katarzyna Kobierska)
+ - Limit the number of retries on error to 3 for exporting projects
+ - Allow empty repositories on project import/export
+ - Render only commit message title in builds (Katarzyna Kobierska Ula Budziszewska)
+ - Allow bulk (un)subscription from issues in issue index
+ - Fix MR diff encoding issues exporting GitLab projects
+ - Export and import avatar as part of project import/export
+
+v 8.9.6
+ - Fix importing of events under notes for GitLab projects. !5154
+ - Fix log statements in import/export. !5129
+ - Fix commit avatar alignment in compare view. !5128
+ - Fix broken migration in MySQL. !5005
+ - Overwrite Host and X-Forwarded-Host headers in NGINX !5213
+ - Keeps issue number when importing from Gitlab.com
+ - Add Pending tab for Builds (Katarzyna Kobierska, Urszula Budziszewska)
+
+v 8.9.7 (unreleased)
+ - Fix import_data wrongly saved as a result of an invalid import_url
+
+v 8.9.6
+ - Fix importing of events under notes for GitLab projects
+
+v 8.9.5
+ - Add more debug info to import/export and memory killer. !5108
+ - Fixed avatar alignment in new MR view. !5095
+ - Fix diff comments not showing up in activity feed. !5069
+ - Add index on both Award Emoji user and name. !5061
+ - Downgrade to Redis 3.2.2 due to massive memory leak with Sidekiq. !5056
+ - Re-enable import button when import process fails due to namespace already being taken. !5053
+ - Fix snippets comments not displayed. !5045
+ - Fix emoji paths in relative root configurations. !5027
+ - Fix issues importing events in Import/Export. !4987
+ - Fixed 'use shortcuts' button on docs. !4979
+ - Admin should be able to turn shared runners into specific ones. !4961
+ - Update RedCloth to 4.3.2 for CVE-2012-6684. !4929 (Takuya Noguchi)
+ - Improve the request / withdraw access button. !4860
+
+v 8.9.4
+ - Fix privilege escalation issue with OAuth external users.
+ - Ensure references to private repos aren't shown to logged-out users.
+ - Fixed search field blur not removing focus. !4704
+ - Resolve "Sub nav isn't showing on file view". !4890
+ - Fixes middle click and double request when navigating through the file browser. !4891
+ - Fixed URL on label button when filtering. !4897
+ - Fixed commit avatar alignment. !4933
+ - Do not show build retry link when build is active. !4967
+ - Fix restore Rake task warning message output. !4980
+ - Handle external issues in IssueReferenceFilter. !4988
+ - Expiry date on pinned nav cookie. !5009
+ - Updated breakpoint for sidebar pinning. !5019
+
+v 8.9.3
+ - Fix encrypted data backwards compatibility after upgrading attr_encrypted gem. !4963
+ - Fix rendering of commit notes. !4953
+ - Resolve "Pin should show up at 1280px min". !4947
+ - Switched mobile button icons to ellipsis and angle. !4944
+ - Correctly returns todo ID after creating todo. !4941
+ - Better debugging for memory killer middleware. !4936
+ - Remove duplicate new page btn from edit wiki. !4904
+ - Use clock_gettime for all performance timestamps. !4899
+ - Use memorized tags array when searching tags by name. !4859
+ - Fixed avatar alignment in new MR view. !4901
+ - Removed fade when filtering results. !4932
+ - Fix missing avatar on system notes. !4954
+ - Reduce overhead and optimize ProjectTeam#max_member_access performance. !4973
+ - Use update_columns to bypass all the dirty code on active_record. !4985
+ - Fix restore Rake task warning message output !4980
+
+v 8.9.2
+ - Fix visibility of snippets when searching.
+ - Fix an information disclosure when requesting access to a group containing private projects.
+ - Update omniauth-saml to 1.6.0 !4951
+
+v 8.9.1
+ - Refactor labels documentation. !3347
+ - Eager load award emoji on notes. !4628
+ - Fix some CI wording in documentation. !4660
+ - Document `GIT_STRATEGY` and `GIT_DEPTH`. !4720
+ - Add documentation for the export & import features. !4732
+ - Add some docs for Docker Registry configuration. !4738
+ - Ensure we don't send the "access request declined" email to access requesters on project deletion. !4744
+ - Display group/project access requesters separately in the admin area. !4798
+ - Add documentation and examples for configuring cloud storage for registry images. !4812
+ - Clarifies documentation about artifact expiry. !4831
+ - Fix the Network graph links. !4832
+ - Fix MR-auto-close text added to description. !4836
+ - Add documentation for award emoji now that comments can be awarded with emojis. !4839
+ - Fix typo in export failure email. !4847
+ - Fix header vertical centering. !4170
+ - Fix subsequent SAML sign ins. !4718
+ - Set button label when picking an option from status dropdown. !4771
+ - Prevent invalid URLs from raising exceptions in WikiLink Filter. !4775
+ - Handle external issues in IssueReferenceFilter. !4789
+ - Support for rendering/redacting multiple documents. !4828
+ - Update Todos documentation and screenshots to include new functionality. !4840
+ - Hide nav arrows by default. !4843
+ - Added bottom padding to label color suggestion link. !4845
+ - Use jQuery objects in ref dropdown. !4850
+ - Fix GitLab project import issues related to notes and builds. !4855
+ - Restrict header logo to 36px so it doesn't overflow. !4861
+ - Fix unwanted label unassignment. !4863
+ - Fix mobile Safari bug where horizontal nav arrows would flicker on scroll. !4869
+ - Restore old behavior around diff notes to outdated discussions. !4870
+ - Fix merge requests project settings help link anchor. !4873
+ - Fix 404 when accessing pipelines as guest user on public projects. !4881
+ - Remove width restriction for logo on sign-in page. !4888
+ - Bump gitlab_git to 10.2.3 to fix false truncated warnings with ISO-8559 files. !4884
+ - Apply selected value as label. !4886
+ - Fix temp file being deleted after the request while importing a GitLab project. !4894
+ - Fix pagination when sorting by columns with lots of ties (like priority)
+ - Implement Subresource Integrity for CSS and JavaScript assets. This prevents malicious assets from loading in the case of a CDN compromise.
+ - Fix user creation with stronger minimum password requirements !4054 (nathan-pmt)
+ - Fix a wrong MR status when merge_when_build_succeeds & project.only_allow_merge_if_build_succeeds are true. !4912
+ - Add SMTP as default delivery method to match gitlab-org/omnibus-gitlab!826. !4915
+ - Remove duplicate 'New Page' button on edit wiki page
+
+v 8.9.0
+ - Fix group visibility form layout in application settings
+ - Fix builds API response not including commit data
- Fix error when CI job variables key specified but not defined
- Fix pipeline status when there are no builds in pipeline
- Fix Error 500 when using closes_issues API with an external issue tracker
@@ -8,17 +254,21 @@ v 8.9.0 (unreleased)
- Bulk assign/unassign labels to issues.
- Ability to prioritize labels !4009 / !3205 (Thijs Wouters)
- Show Star and Fork buttons on mobile.
+ - Performance improvements on RelativeLinkFilter
- Fix endless redirections when accessing user OAuth applications when they are disabled
- Allow enabling wiki page events from Webhook management UI
- Bump rouge to 1.11.0
- Fix issue with arrow keys not working in search autocomplete dropdown
- Fix an issue where note polling stopped working if a window was in the
background during a refresh.
+ - Pre-processing Markdown now only happens when needed
- Make EmailsOnPushWorker use Sidekiq mailers queue
- Redesign all Devise emails. !4297
- Don't show 'Leave Project' to group members
- Fix wiki page events' webhook to point to the wiki repository
+ - Add a border around images to differentiate them from the background.
- Don't show tags for revert and cherry-pick operations
+ - Show image ID on registry page
- Fix issue todo not remove when leave project !4150 (Long Nguyen)
- Allow customisable text on the 'nearly there' page after a user signs up
- Bump recaptcha gem to 3.0.0 to remove deprecated stoken support
@@ -56,9 +306,11 @@ v 8.9.0 (unreleased)
- Add `sha` parameter to MR merge API, to ensure only reviewed changes are merged
- Don't allow MRs to be merged when commits were added since the last review / page load
- Add DB index on users.state
+ - Limit email on push diff size to 30 files / 150 KB
- Add rake task 'gitlab:db:configure' for conditionally seeding or migrating the database
- Changed the Slack build message to use the singular duration if necessary (Aran Koning)
- Fix race condition on merge when build succeeds
+ - Added shortcut to focus filter search fields and added documentation #18120
- Links from a wiki page to other wiki pages should be rewritten as expected
- Add option to project to only allow merge requests to be merged if the build succeeds (Rui Santos)
- Added navigation shortcuts to the project pipelines, milestones, builds and forks page. !4393
@@ -70,6 +322,7 @@ v 8.9.0 (unreleased)
- Todos will display target state if issuable target is 'Closed' or 'Merged'
- Validate only and except regexp
- Fix bug when sorting issues by milestone due date and filtering by two or more labels
+ - POST to API /projects/:id/runners/:runner_id would give 409 if the runner was already enabled for this project
- Add support for using Yubikeys (U2F) for two-factor authentication
- Link to blank group icon doesn't throw a 404 anymore
- Remove 'main language' feature
@@ -77,16 +330,20 @@ v 8.9.0 (unreleased)
- Pipelines can be canceled only when there are running builds
- Allow authentication using personal access tokens
- Use downcased path to container repository as this is expected path by Docker
+ - Allow to use CI token to fetch LFS objects
- Custom notification settings
- Projects pending deletion will render a 404 page
- Measure queue duration between gitlab-workhorse and Rails
- Added Gfm autocomplete for labels
+ - Added edit note 'up' shortcut documentation to the help panel and docs screenshot #18114
- Make Omniauth providers specs to not modify global configuration
- Remove unused JiraIssue class and replace references with ExternalIssue. !4659 (Ilan Shamir)
- Make authentication service for Container Registry to be compatible with < Docker 1.11
+ - Make it possible to lock a runner from being enabled for other projects
- 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
@@ -101,6 +358,7 @@ v 8.9.0 (unreleased)
- An indicator is now displayed at the top of the comment field for confidential issues.
- Show categorised search queries in the search autocomplete
- RepositoryCheck::SingleRepositoryWorker public and private methods are now instrumented
+ - Dropdown for `.gitlab-ci.yml` templates
- Improve issuables APIs performance when accessing notes !4471
- Add sorting dropdown to tags page !4423
- External links now open in a new tab
@@ -129,10 +387,26 @@ v 8.9.0 (unreleased)
- Various associations are now eager loaded when parsing issue references to reduce the number of queries executed
- Set inverse_of for Project/Service association to reduce the number of queries
- Update tanuki logo highlight/loading colors
+ - Remove explicit Gitlab::Metrics.action assignments, are already automatic.
- Use Git cached counters for branches and tags on project page
+ - Cache participable participants in an instance variable.
- 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
@@ -264,6 +538,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
@@ -1990,8 +2272,6 @@ v 7.7.0
- Fixes for edit comments: drag-n-drop images, preview mode, selecting images, save & update
- Remove password strength indicator
-
-
v 7.6.0
- Fork repository to groups
- New rugged version
@@ -2417,13 +2697,13 @@ v 6.5.0
- Files API supports base64 encoded content (sponsored by O'Reilly Media)
- Added support for Go's repository retrieval (Bruno Albuquerque)
-v6.4.3
+v 6.4.3
- Don't use unicorn worker killer if PhusionPassenger is defined
-v6.4.2
+v 6.4.2
- Fixed wrong behaviour of script/upgrade.rb
-v6.4.1
+v 6.4.1
- Fixed bug with repository rename
- Fixed bug with project transfer
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f4472214778..14ff05c9aa3 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -145,7 +145,8 @@ might be edited to make them small and simple.
You are encouraged to use the template below for feature proposals.
```
-## Description including problem, use cases, benefits, and/or goals
+## Description
+Include problem, use cases, benefits, and/or goals
## Proposal
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 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..e7c7d3cc3c8 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-0.7.5
+0.7.8
diff --git a/Gemfile b/Gemfile
index bc1223e1bbc..c5df68839d5 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,6 +1,6 @@
-source "https://rubygems.org"
+source 'https://rubygems.org'
-gem 'rails', '4.2.6'
+gem 'rails', '4.2.7'
gem 'rails-deprecated_sanitizer', '~> 1.0.3'
# Responders respond_to and respond_with
@@ -11,15 +11,15 @@ gem 'responders', '~> 2.0'
gem 'sprockets', '~> 3.6.0'
# Default values for AR models
-gem "default_value_for", "~> 3.0.0"
+gem 'default_value_for', '~> 3.0.0'
# Supported DBs
-gem "mysql2", '~> 0.3.16', group: :mysql
-gem "pg", '~> 0.18.2', group: :postgres
+gem 'mysql2', '~> 0.3.16', group: :mysql
+gem 'pg', '~> 0.18.2', group: :postgres
# Authentication libraries
gem 'devise', '~> 4.0'
-gem 'doorkeeper', '~> 3.1'
+gem 'doorkeeper', '~> 4.0'
gem 'omniauth', '~> 1.3.1'
gem 'omniauth-auth0', '~> 1.4.1'
gem 'omniauth-azure-oauth2', '~> 0.0.6'
@@ -28,9 +28,9 @@ gem 'omniauth-cas3', '~> 1.1.2'
gem 'omniauth-facebook', '~> 3.0.0'
gem 'omniauth-github', '~> 1.1.1'
gem 'omniauth-gitlab', '~> 1.0.0'
-gem 'omniauth-google-oauth2', '~> 0.2.0'
+gem 'omniauth-google-oauth2', '~> 0.4.1'
gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
-gem 'omniauth-saml', '~> 1.5.0'
+gem 'omniauth-saml', '~> 1.6.0'
gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0'
gem 'omniauth_crowd', '~> 2.2.0'
@@ -48,24 +48,24 @@ gem 'attr_encrypted', '~> 3.0.0'
gem 'u2f', '~> 0.2.1'
# Browser detection
-gem "browser", '~> 2.0.3'
+gem 'browser', '~> 2.2'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
-gem "gitlab_git", '~> 10.2'
+gem 'gitlab_git', '~> 10.3.2'
# LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes
# see https://github.com/intridea/omniauth-ldap/compare/master...gitlabhq:master
-gem 'gitlab_omniauth-ldap', '~> 1.2.1', require: "omniauth-ldap"
+gem 'gitlab_omniauth-ldap', '~> 1.2.1', require: 'omniauth-ldap'
# Git Wiki
# Required manually in config/initializers/gollum.rb to control load order
-gem 'gollum-lib', '~> 4.1.0', require: false
+gem 'gollum-lib', '~> 4.2', require: false
gem 'gollum-rugged_adapter', '~> 0.4.2', require: false
# Language detection
-gem "github-linguist", "~> 4.7.0", require: "linguist"
+gem 'github-linguist', '~> 4.7.0', require: 'linguist'
# API
gem 'grape', '~> 0.13.0'
@@ -73,13 +73,13 @@ gem 'grape-entity', '~> 0.4.2'
gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
# Pagination
-gem "kaminari", "~> 0.17.0"
+gem 'kaminari', '~> 0.17.0'
# HAML
-gem "haml-rails", '~> 0.9.0'
+gem 'hamlit', '~> 2.5'
# Files attachments
-gem "carrierwave", '~> 0.10.0'
+gem 'carrierwave', '~> 0.10.0'
# Drag and Drop UI
gem 'dropzonejs-rails', '~> 0.7.1'
@@ -91,28 +91,29 @@ gem 'fog-core', '~> 1.40'
gem 'fog-local', '~> 0.3'
gem 'fog-google', '~> 0.3'
gem 'fog-openstack', '~> 0.1'
+gem 'fog-rackspace', '~> 0.1.1'
# for aws storage
-gem "unf", '~> 0.1.4'
+gem 'unf', '~> 0.1.4'
# Authorization
-gem "six", '~> 0.2.0'
+gem 'six', '~> 0.2.0'
# Seed data
-gem "seed-fu", '~> 2.3.5'
+gem 'seed-fu', '~> 2.3.5'
# Markdown and HTML processing
gem 'html-pipeline', '~> 1.11.0'
gem 'task_list', '~> 1.0.2', require: 'task_list/railtie'
-gem 'github-markup', '~> 1.3.1'
+gem 'github-markup', '~> 1.4'
gem 'redcarpet', '~> 3.3.3'
-gem 'RedCloth', '~> 4.2.9'
+gem 'RedCloth', '~> 4.3.2'
gem 'rdoc', '~>3.6'
gem 'org-ruby', '~> 0.9.12'
gem 'creole', '~> 0.5.0'
gem 'wikicloth', '0.8.1'
gem 'asciidoctor', '~> 1.5.2'
-gem 'rouge', '~> 1.11'
+gem 'rouge', '~> 2.0'
# See https://groups.google.com/forum/#!topic/ruby-security-ann/aSbgDiwb24s
# and https://groups.google.com/forum/#!topic/ruby-security-ann/Dy7YiKb_pMM
@@ -123,29 +124,29 @@ gem 'diffy', '~> 3.0.3'
# Application server
group :unicorn do
- gem "unicorn", '~> 4.9.0'
+ gem 'unicorn', '~> 4.9.0'
gem 'unicorn-worker-killer', '~> 0.4.2'
end
# State machine
-gem "state_machines-activerecord", '~> 0.4.0'
+gem 'state_machines-activerecord', '~> 0.4.0'
# Run events after state machine commits
-gem 'after_commit_queue'
+gem 'after_commit_queue', '~> 1.3.0'
# Issue tags
gem 'acts-as-taggable-on', '~> 3.4'
# Background jobs
-gem 'sinatra', '~> 1.4.4', require: nil
+gem 'sinatra', '~> 1.4.4', require: false
gem 'sidekiq', '~> 4.0'
gem 'sidekiq-cron', '~> 0.4.0'
-gem 'redis-namespace'
+gem 'redis-namespace', '~> 1.5.2'
# HTTP requests
-gem "httparty", '~> 0.13.3'
+gem 'httparty', '~> 0.13.3'
# Colored output to console
-gem "rainbow", '~> 2.1.0'
+gem 'rainbow', '~> 2.1.0'
# GitLab settings
gem 'settingslogic', '~> 2.0.9'
@@ -155,7 +156,7 @@ gem 'settingslogic', '~> 2.0.9'
gem 'version_sorter', '~> 2.0.0'
# Cache
-gem "redis-rails", '~> 4.0.0'
+gem 'redis-rails', '~> 4.0.0'
# Redis
gem 'redis', '~> 3.2'
@@ -168,13 +169,13 @@ gem 'tinder', '~> 1.10.0'
gem 'hipchat', '~> 1.5.0'
# Flowdock integration
-gem "gitlab-flowdock-git-hook", "~> 1.0.1"
+gem 'gitlab-flowdock-git-hook', '~> 1.0.1'
# Gemnasium integration
-gem "gemnasium-gitlab-service", "~> 0.2"
+gem 'gemnasium-gitlab-service', '~> 0.2'
# Slack integration
-gem "slack-notifier", "~> 1.2.0"
+gem 'slack-notifier', '~> 1.2.0'
# Asana integration
gem 'asana', '~> 0.4.0'
@@ -186,20 +187,20 @@ gem 'ruby-fogbugz', '~> 0.2.1'
gem 'd3_rails', '~> 3.5.0'
# underscore-rails
-gem "underscore-rails", "~> 1.8.0"
+gem 'underscore-rails', '~> 1.8.0'
# Sanitize user input
-gem "sanitize", '~> 2.0'
+gem 'sanitize', '~> 2.0'
gem 'babosa', '~> 1.0.2'
# Sanitizes SVG input
-gem "loofah", "~> 2.0.3"
+gem 'loofah', '~> 2.0.3'
# Working with license
gem 'licensee', '~> 8.0.0'
# Protect against bruteforcing
-gem "rack-attack", '~> 4.3.1'
+gem 'rack-attack', '~> 4.3.1'
# Ace editor
gem 'ace-rails-ap', '~> 4.0.2'
@@ -213,16 +214,16 @@ gem 'charlock_holmes', '~> 0.7.3'
# Parse duration
gem 'chronic_duration', '~> 0.10.6'
-gem "sass-rails", '~> 5.0.0'
-gem "coffee-rails", '~> 4.1.0'
-gem "uglifier", '~> 2.7.2'
+gem 'sass-rails', '~> 5.0.0'
+gem 'coffee-rails', '~> 4.1.0'
+gem 'uglifier', '~> 2.7.2'
gem 'turbolinks', '~> 2.5.0'
gem 'jquery-turbolinks', '~> 2.1.0'
gem 'addressable', '~> 2.3.8'
gem 'bootstrap-sass', '~> 3.3.0'
gem 'font-awesome-rails', '~> 4.6.1'
-gem 'gitlab_emoji', '~> 0.3.0'
+gem 'gemojione', '~> 3.0'
gem 'gon', '~> 6.0.1'
gem 'jquery-atwho-rails', '~> 1.3.2'
gem 'jquery-rails', '~> 4.1.0'
@@ -234,7 +235,7 @@ gem 'net-ssh', '~> 3.0.1'
gem 'base32', '~> 0.3.0'
# Sentry integration
-gem 'sentry-raven', '~> 0.15'
+gem 'sentry-raven', '~> 1.1.0'
gem 'premailer-rails', '~> 1.9.0'
@@ -246,14 +247,13 @@ group :metrics do
end
group :development do
- gem "foreman"
+ gem 'foreman', '~> 0.78.0'
gem 'brakeman', '~> 3.3.0', require: false
gem 'letter_opener_web', '~> 1.3.0'
- gem 'quiet_assets', '~> 1.0.2'
gem 'rerun', '~> 0.11.0'
- gem 'bullet', require: false
- gem 'rblineprof', platform: :mri, require: false
+ gem 'bullet', '~> 5.0.0', require: false
+ gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false
gem 'web-console', '~> 2.0'
# Better errors handler
@@ -261,23 +261,23 @@ group :development do
gem 'binding_of_caller', '~> 0.7.2'
# Docs generator
- gem "sdoc", '~> 0.3.20'
+ gem 'sdoc', '~> 0.3.20'
# thin instead webrick
- gem 'thin', '~> 1.6.1'
+ gem 'thin', '~> 1.7.0'
end
group :development, :test do
- gem 'byebug', platform: :mri
- gem 'pry-rails'
+ gem 'byebug', '~> 8.2.1', platform: :mri
+ gem 'pry-rails', '~> 0.3.4'
gem 'awesome_print', '~> 1.2.0', require: false
gem 'fuubar', '~> 2.0.0'
gem 'database_cleaner', '~> 1.4.0'
gem 'factory_girl_rails', '~> 4.6.0'
- gem 'rspec-rails', '~> 3.4.0'
- gem 'rspec-retry'
+ gem 'rspec-rails', '~> 3.5.0'
+ gem 'rspec-retry', '~> 0.4.5'
gem 'spinach-rails', '~> 0.2.1'
gem 'spinach-rerun-reporter', '~> 0.0.2'
@@ -299,19 +299,18 @@ group :development, :test do
gem 'spring-commands-spinach', '~> 1.1.0'
gem 'spring-commands-teaspoon', '~> 0.0.2'
- gem 'rubocop', '~> 0.40.0', require: false
+ gem 'rubocop', '~> 0.41.2', require: false
gem 'rubocop-rspec', '~> 1.5.0', require: false
gem 'scss_lint', '~> 0.47.0', require: false
- gem 'coveralls', '~> 0.8.2', require: false
gem 'simplecov', '~> 0.11.0', require: false
- gem 'flog', require: false
- gem 'flay', require: false
- gem 'bundler-audit', require: false
+ gem 'flog', '~> 4.3.2', require: false
+ gem 'flay', '~> 2.6.1', require: false
+ gem 'bundler-audit', '~> 0.5.0', require: false
- gem 'benchmark-ips', require: false
+ gem 'benchmark-ips', '~> 2.3.0', require: false
- gem "license_finder", require: false
- gem 'knapsack'
+ gem 'license_finder', '~> 2.1.0', require: false
+ gem 'knapsack', '~> 1.11.0'
end
group :test do
@@ -319,30 +318,37 @@ 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.7"
+gem 'mail_room', '~> 0.8'
gem 'email_reply_parser', '~> 0.5.8'
## CI
gem 'activerecord-session_store', '~> 1.0.0'
-gem "nested_form", '~> 0.3.2'
+gem 'nested_form', '~> 0.3.2'
# OAuth
-gem 'oauth2', '~> 1.0.0'
+gem 'oauth2', '~> 1.2.0'
# Soft deletion
-gem "paranoia", "~> 2.0"
+gem 'paranoia', '~> 2.0'
# Health check
-gem 'health_check', '~> 1.5.1'
+gem 'health_check', '~> 2.1.0'
+
+# System information
+gem 'vmstat', '~> 2.1.0'
+gem 'sys-filesystem', '~> 1.1.6'
+
+# Secure headers for Content Security Policy
+gem 'secure_headers', '~> 3.3'
diff --git a/Gemfile.lock b/Gemfile.lock
index 49e548fb94f..363904a4baa 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,36 +1,36 @@
GEM
remote: https://rubygems.org/
specs:
- RedCloth (4.2.9)
+ RedCloth (4.3.2)
ace-rails-ap (4.0.2)
- actionmailer (4.2.6)
- actionpack (= 4.2.6)
- actionview (= 4.2.6)
- activejob (= 4.2.6)
+ actionmailer (4.2.7)
+ actionpack (= 4.2.7)
+ actionview (= 4.2.7)
+ activejob (= 4.2.7)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 1.0, >= 1.0.5)
- actionpack (4.2.6)
- actionview (= 4.2.6)
- activesupport (= 4.2.6)
+ actionpack (4.2.7)
+ actionview (= 4.2.7)
+ activesupport (= 4.2.7)
rack (~> 1.6)
rack-test (~> 0.6.2)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
- actionview (4.2.6)
- activesupport (= 4.2.6)
+ actionview (4.2.7)
+ activesupport (= 4.2.7)
builder (~> 3.1)
erubis (~> 2.7.0)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
- activejob (4.2.6)
- activesupport (= 4.2.6)
+ activejob (4.2.7)
+ activesupport (= 4.2.7)
globalid (>= 0.3.0)
- activemodel (4.2.6)
- activesupport (= 4.2.6)
+ activemodel (4.2.7)
+ activesupport (= 4.2.7)
builder (~> 3.1)
- activerecord (4.2.6)
- activemodel (= 4.2.6)
- activesupport (= 4.2.6)
+ activerecord (4.2.7)
+ activemodel (= 4.2.7)
+ activesupport (= 4.2.7)
arel (~> 6.0)
activerecord-session_store (1.0.0)
actionpack (>= 4.0, < 5.1)
@@ -38,7 +38,7 @@ GEM
multi_json (~> 1.11, >= 1.11.2)
rack (>= 1.5.2, < 3)
railties (>= 4.0, < 5.1)
- activesupport (4.2.6)
+ activesupport (4.2.7)
i18n (~> 0.7)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
@@ -58,7 +58,7 @@ GEM
faraday_middleware-multi_json (~> 0.0)
oauth2 (~> 1.0)
asciidoctor (1.5.3)
- ast (2.2.0)
+ ast (2.3.0)
attr_encrypted (3.0.1)
encryptor (~> 3.0.0)
attr_required (1.0.0)
@@ -98,7 +98,7 @@ GEM
autoprefixer-rails (>= 5.2.1)
sass (>= 3.3.4)
brakeman (3.3.2)
- browser (2.0.3)
+ browser (2.2.0)
builder (3.2.2)
bullet (5.0.0)
activesupport (>= 3.0.0)
@@ -141,12 +141,6 @@ GEM
colorize (0.7.7)
concurrent-ruby (1.0.2)
connection_pool (2.2.0)
- coveralls (0.8.13)
- json (~> 1.8)
- simplecov (~> 0.11.0)
- term-ansicolor (~> 1.3)
- thor (~> 0.19.1)
- tins (~> 1.6.0)
crack (0.4.3)
safe_yaml (~> 1.0.0)
creole (0.5.0)
@@ -177,8 +171,8 @@ GEM
diff-lcs (1.2.5)
diffy (3.0.7)
docile (1.1.5)
- doorkeeper (3.1.0)
- railties (>= 3.2)
+ doorkeeper (4.0.0)
+ railties (>= 4.2)
dropzonejs-rails (0.7.2)
rails (> 3.1)
email_reply_parser (0.5.8)
@@ -243,6 +237,11 @@ GEM
fog-core (>= 1.39)
fog-json (>= 1.0)
ipaddress (>= 0.8)
+ fog-rackspace (0.1.1)
+ fog-core (>= 1.35)
+ fog-json (>= 1.0)
+ fog-xml (>= 0.1)
+ ipaddress (>= 0.8)
fog-xml (0.1.2)
fog-core
nokogiri (~> 1.5, >= 1.5.11)
@@ -256,7 +255,7 @@ GEM
ruby-progressbar (~> 1.4)
gemnasium-gitlab-service (0.2.6)
rugged (~> 0.21)
- gemojione (2.2.1)
+ gemojione (3.0.1)
json
get_process_mem (0.2.0)
gherkin-ruby (0.3.2)
@@ -265,7 +264,7 @@ GEM
escape_utils (~> 1.1.0)
mime-types (>= 1.19)
rugged (>= 0.23.0b)
- github-markup (1.3.3)
+ github-markup (1.4.0)
gitlab-flowdock-git-hook (1.0.1)
flowdock (~> 0.7)
gitlab-grit (>= 2.4.1)
@@ -275,9 +274,7 @@ GEM
diff-lcs (~> 1.1)
mime-types (>= 1.16, < 3)
posix-spawn (~> 0.3)
- gitlab_emoji (0.3.1)
- gemojione (~> 2.2, >= 2.2.1)
- gitlab_git (10.2.0)
+ gitlab_git (10.3.2)
activesupport (~> 4.0)
charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0)
@@ -290,13 +287,13 @@ GEM
rubyntlm (~> 0.3)
globalid (0.3.6)
activesupport (>= 4.1.0)
- gollum-grit_adapter (1.0.0)
+ gollum-grit_adapter (1.0.1)
gitlab-grit (~> 2.7, >= 2.7.1)
- gollum-lib (4.1.0)
- github-markup (~> 1.3.3)
+ gollum-lib (4.2.1)
+ github-markup (~> 1.4.0)
gollum-grit_adapter (~> 1.0)
nokogiri (~> 1.6.4)
- rouge (~> 1.9)
+ rouge (~> 2.0)
sanitize (~> 2.1.0)
stringex (~> 2.5.1)
gollum-rugged_adapter (0.4.2)
@@ -320,28 +317,19 @@ GEM
grape-entity (0.4.8)
activesupport
multi_json (>= 1.3.2)
- haml (4.0.7)
+ hamlit (2.5.0)
+ temple (~> 0.7.6)
+ thor
tilt
- haml-rails (0.9.0)
- actionpack (>= 4.0.1)
- activesupport (>= 4.0.1)
- haml (>= 4.0.6, < 5.0)
- html2haml (>= 1.0.1)
- railties (>= 4.0.1)
hashie (3.4.3)
- health_check (1.5.1)
- rails (>= 2.3.0)
+ health_check (2.1.0)
+ rails (>= 4.0)
hipchat (1.5.2)
httparty
mimemagic
html-pipeline (1.11.0)
activesupport (>= 2)
nokogiri (~> 1.4)
- html2haml (2.0.0)
- erubis (~> 2.7.0)
- haml (~> 4.0.0)
- nokogiri (~> 1.6.0)
- ruby_parser (~> 3.5)
htmlentities (4.3.4)
http_parser.rb (0.5.3)
httparty (0.13.7)
@@ -365,7 +353,7 @@ GEM
jquery-ui-rails (5.0.5)
railties (>= 3.2.16)
json (1.8.3)
- jwt (1.5.2)
+ jwt (1.5.4)
kaminari (0.17.0)
actionpack (>= 3.0.0)
activesupport (>= 3.0.0)
@@ -398,14 +386,14 @@ GEM
systemu (~> 2.6.2)
mail (2.6.4)
mime-types (>= 1.16, < 4)
- mail_room (0.7.0)
+ mail_room (0.8.0)
method_source (0.8.2)
mime-types (2.99.2)
mimemagic (0.3.0)
mini_portile2 (2.1.0)
minitest (5.7.0)
mousetrap-rails (1.4.6)
- multi_json (1.11.2)
+ multi_json (1.12.1)
multi_xml (0.5.5)
multipart-post (2.0.0)
mysql2 (0.3.20)
@@ -418,12 +406,12 @@ GEM
pkg-config (~> 1.1.7)
numerizer (0.1.1)
oauth (0.4.7)
- oauth2 (1.0.0)
+ oauth2 (1.2.0)
faraday (>= 0.8, < 0.10)
jwt (~> 1.0)
multi_json (~> 1.3)
multi_xml (~> 0.5)
- rack (~> 1.2)
+ rack (>= 1.2, < 3)
octokit (4.3.0)
sawyer (~> 0.7.0, >= 0.5.3)
omniauth (1.3.1)
@@ -451,7 +439,7 @@ GEM
omniauth-gitlab (1.0.1)
omniauth (~> 1.0)
omniauth-oauth2 (~> 1.0)
- omniauth-google-oauth2 (0.2.10)
+ omniauth-google-oauth2 (0.4.1)
addressable (~> 2.3)
jwt (~> 1.0)
multi_json (~> 1.3)
@@ -468,9 +456,9 @@ GEM
omniauth-oauth2 (1.3.1)
oauth2 (~> 1.0)
omniauth (~> 1.2)
- omniauth-saml (1.5.0)
+ omniauth-saml (1.6.0)
omniauth (~> 1.3)
- ruby-saml (~> 1.1, >= 1.1.1)
+ ruby-saml (~> 1.3)
omniauth-shibboleth (1.2.1)
omniauth (>= 1.0.0)
omniauth-twitter (1.2.1)
@@ -485,7 +473,7 @@ GEM
orm_adapter (0.5.0)
paranoia (2.1.4)
activerecord (~> 4.0)
- parser (2.3.1.0)
+ parser (2.3.1.2)
ast (~> 2.2)
pg (0.18.4)
pkg-config (1.1.7)
@@ -509,8 +497,6 @@ GEM
pry-rails (0.3.4)
pry (>= 0.9.10)
pyu-ruby-sasl (0.0.3.3)
- quiet_assets (1.0.3)
- railties (>= 3.1, < 5.0)
rack (1.6.4)
rack-accept (0.4.5)
rack (>= 0.4)
@@ -529,16 +515,16 @@ GEM
rack
rack-test (0.6.3)
rack (>= 1.0)
- rails (4.2.6)
- actionmailer (= 4.2.6)
- actionpack (= 4.2.6)
- actionview (= 4.2.6)
- activejob (= 4.2.6)
- activemodel (= 4.2.6)
- activerecord (= 4.2.6)
- activesupport (= 4.2.6)
+ rails (4.2.7)
+ actionmailer (= 4.2.7)
+ actionpack (= 4.2.7)
+ actionview (= 4.2.7)
+ activejob (= 4.2.7)
+ activemodel (= 4.2.7)
+ activerecord (= 4.2.7)
+ activesupport (= 4.2.7)
bundler (>= 1.3.0, < 2.0)
- railties (= 4.2.6)
+ railties (= 4.2.7)
sprockets-rails
rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha)
@@ -548,9 +534,9 @@ GEM
rails-deprecated_sanitizer (>= 1.0.1)
rails-html-sanitizer (1.0.3)
loofah (~> 2.0)
- railties (4.2.6)
- actionpack (= 4.2.6)
- activesupport (= 4.2.6)
+ railties (4.2.7)
+ actionpack (= 4.2.7)
+ activesupport (= 4.2.7)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rainbow (2.1.0)
@@ -566,7 +552,7 @@ GEM
recaptcha (3.0.0)
json
redcarpet (3.3.3)
- redis (3.3.0)
+ redis (3.2.2)
redis-actionpack (4.0.1)
actionpack (~> 4)
redis-rack (~> 1.5.0)
@@ -590,38 +576,38 @@ GEM
listen (~> 3.0)
responders (2.1.1)
railties (>= 4.2.0, < 5.1)
- rinku (1.7.3)
+ rinku (2.0.0)
rotp (2.1.2)
- rouge (1.11.0)
+ rouge (2.0.5)
rqrcode (0.7.0)
chunky_png
rqrcode-rails3 (0.1.7)
rqrcode (>= 0.4.2)
- rspec (3.4.0)
- rspec-core (~> 3.4.0)
- rspec-expectations (~> 3.4.0)
- rspec-mocks (~> 3.4.0)
- rspec-core (3.4.4)
- rspec-support (~> 3.4.0)
- rspec-expectations (3.4.0)
+ rspec (3.5.0)
+ rspec-core (~> 3.5.0)
+ rspec-expectations (~> 3.5.0)
+ rspec-mocks (~> 3.5.0)
+ rspec-core (3.5.0)
+ rspec-support (~> 3.5.0)
+ rspec-expectations (3.5.0)
diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.4.0)
- rspec-mocks (3.4.1)
+ rspec-support (~> 3.5.0)
+ rspec-mocks (3.5.0)
diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.4.0)
- rspec-rails (3.4.2)
- actionpack (>= 3.0, < 4.3)
- activesupport (>= 3.0, < 4.3)
- railties (>= 3.0, < 4.3)
- rspec-core (~> 3.4.0)
- rspec-expectations (~> 3.4.0)
- rspec-mocks (~> 3.4.0)
- rspec-support (~> 3.4.0)
+ rspec-support (~> 3.5.0)
+ rspec-rails (3.5.0)
+ actionpack (>= 3.0)
+ activesupport (>= 3.0)
+ railties (>= 3.0)
+ rspec-core (~> 3.5.0)
+ rspec-expectations (~> 3.5.0)
+ rspec-mocks (~> 3.5.0)
+ rspec-support (~> 3.5.0)
rspec-retry (0.4.5)
rspec-core
- rspec-support (3.4.1)
- rubocop (0.40.0)
- parser (>= 2.3.1.0, < 3.0)
+ rspec-support (3.5.0)
+ rubocop (0.41.2)
+ parser (>= 2.3.1.1, < 3.0)
powerpack (~> 0.1)
rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.7)
@@ -631,9 +617,8 @@ GEM
ruby-fogbugz (0.2.1)
crack (~> 0.4)
ruby-progressbar (1.8.1)
- ruby-saml (1.1.2)
+ ruby-saml (1.3.0)
nokogiri (>= 1.5.10)
- uuid (~> 2.3)
ruby_parser (3.8.2)
sexp_processor (~> 4.1)
rubyntlm (0.5.2)
@@ -645,8 +630,8 @@ GEM
sanitize (2.1.0)
nokogiri (>= 1.4.4)
sass (3.4.22)
- sass-rails (5.0.4)
- railties (>= 4.0.0, < 5.0)
+ sass-rails (5.0.5)
+ railties (>= 4.0.0, < 6)
sass (~> 3.1)
sprockets (>= 2.8, < 4.0)
sprockets-rails (>= 2.0, < 4.0)
@@ -660,12 +645,14 @@ 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)
+ secure_headers (3.3.2)
+ useragent
+ seed-fu (2.3.6)
+ activerecord (>= 3.1)
+ activesupport (>= 3.1)
select2-rails (3.5.9.3)
thor (~> 0.14)
- sentry-raven (0.15.6)
+ sentry-raven (1.1.0)
faraday (>= 0.7.6)
settingslogic (2.0.9)
sexp_processor (4.7.0)
@@ -673,10 +660,11 @@ GEM
rack
shoulda-matchers (2.8.0)
activesupport (>= 3.0.0)
- sidekiq (4.1.2)
+ sidekiq (4.1.4)
concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0)
redis (~> 3.2, >= 3.2.1)
+ sinatra (>= 1.4.7)
sidekiq-cron (0.4.0)
redis-namespace (>= 1.5.2)
rufus-scheduler (>= 2.0.24)
@@ -687,8 +675,8 @@ GEM
json (~> 1.8)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.0)
- sinatra (1.4.6)
- rack (~> 1.4)
+ sinatra (1.4.7)
+ rack (~> 1.5)
rack-protection (~> 1.4)
tilt (>= 1.3, < 3)
six (0.2.0)
@@ -704,17 +692,17 @@ GEM
spinach (>= 0.4)
spinach-rerun-reporter (0.0.2)
spinach (~> 0.8)
- spring (1.7.1)
+ spring (1.7.2)
spring-commands-rspec (1.0.4)
spring (>= 0.9.1)
spring-commands-spinach (1.1.0)
spring (>= 0.9.1)
spring-commands-teaspoon (0.0.2)
spring (>= 0.9.1)
- sprockets (3.6.0)
+ sprockets (3.6.3)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
- sprockets-rails (3.0.4)
+ sprockets-rails (3.1.1)
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
@@ -726,6 +714,8 @@ GEM
activerecord (>= 4.1, < 5.1)
state_machines-activemodel (>= 0.3.0)
stringex (2.5.2)
+ sys-filesystem (1.1.6)
+ ffi
systemu (2.6.5)
task_list (1.0.2)
html-pipeline
@@ -733,14 +723,13 @@ GEM
railties (>= 3.2.5, < 6)
teaspoon-jasmine (2.2.0)
teaspoon (>= 1.0.0)
- term-ansicolor (1.3.2)
- tins (~> 1.0)
+ temple (0.7.7)
test_after_commit (0.4.2)
activerecord (>= 3.2)
- thin (1.6.4)
+ thin (1.7.0)
daemons (~> 1.0, >= 1.0.9)
eventmachine (~> 1.0, >= 1.0.4)
- rack (~> 1.0)
+ rack (>= 1, < 3)
thor (0.19.1)
thread_safe (0.3.5)
tilt (2.0.5)
@@ -755,7 +744,6 @@ GEM
mime-types
multi_json (~> 1.7)
twitter-stream (~> 0.1)
- tins (1.6.0)
turbolinks (2.5.3)
coffee-rails
twitter-stream (0.1.16)
@@ -772,7 +760,7 @@ GEM
unf (0.1.4)
unf_ext
unf_ext (0.0.7.2)
- unicode-display_width (1.0.5)
+ unicode-display_width (1.1.0)
unicorn (4.9.0)
kgio (~> 2.6)
rack
@@ -781,6 +769,7 @@ GEM
get_process_mem (~> 0)
unicorn (>= 4, < 6)
uniform_notifier (1.9.0)
+ useragent (0.16.7)
uuid (2.3.8)
macaddr (~> 1.0)
version_sorter (2.0.0)
@@ -789,6 +778,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)
@@ -814,12 +804,12 @@ PLATFORMS
ruby
DEPENDENCIES
- RedCloth (~> 4.2.9)
+ RedCloth (~> 4.3.2)
ace-rails-ap (~> 4.0.2)
activerecord-session_store (~> 1.0.0)
acts-as-taggable-on (~> 3.4)
addressable (~> 2.3.8)
- after_commit_queue
+ after_commit_queue (~> 1.3.0)
akismet (~> 2.0)
allocations (~> 1.0)
asana (~> 0.4.0)
@@ -828,15 +818,15 @@ DEPENDENCIES
awesome_print (~> 1.2.0)
babosa (~> 1.0.2)
base32 (~> 0.3.0)
- benchmark-ips
+ benchmark-ips (~> 2.3.0)
better_errors (~> 1.0.1)
binding_of_caller (~> 0.7.2)
bootstrap-sass (~> 3.3.0)
brakeman (~> 3.3.0)
- browser (~> 2.0.3)
- bullet
- bundler-audit
- byebug
+ browser (~> 2.2)
+ bullet (~> 5.0.0)
+ bundler-audit (~> 0.5.0)
+ byebug (~> 8.2.1)
capybara (~> 2.6.2)
capybara-screenshot (~> 1.0.0)
carrierwave (~> 0.10.0)
@@ -844,7 +834,6 @@ DEPENDENCIES
chronic_duration (~> 0.10.6)
coffee-rails (~> 4.1.0)
connection_pool (~> 2.0)
- coveralls (~> 0.8.2)
creole (~> 0.5.0)
d3_rails (~> 3.5.0)
database_cleaner (~> 1.4.0)
@@ -852,38 +841,39 @@ DEPENDENCIES
devise (~> 4.0)
devise-two-factor (~> 3.0.0)
diffy (~> 3.0.3)
- doorkeeper (~> 3.1)
+ doorkeeper (~> 4.0)
dropzonejs-rails (~> 0.7.1)
email_reply_parser (~> 0.5.8)
email_spec (~> 1.6.0)
factory_girl_rails (~> 4.6.0)
ffaker (~> 2.0.0)
- flay
- flog
+ flay (~> 2.6.1)
+ flog (~> 4.3.2)
fog-aws (~> 0.9)
fog-azure (~> 0.0)
fog-core (~> 1.40)
fog-google (~> 0.3)
fog-local (~> 0.3)
fog-openstack (~> 0.1)
+ fog-rackspace (~> 0.1.1)
font-awesome-rails (~> 4.6.1)
- foreman
+ foreman (~> 0.78.0)
fuubar (~> 2.0.0)
gemnasium-gitlab-service (~> 0.2)
+ gemojione (~> 3.0)
github-linguist (~> 4.7.0)
- github-markup (~> 1.3.1)
+ github-markup (~> 1.4)
gitlab-flowdock-git-hook (~> 1.0.1)
- gitlab_emoji (~> 0.3.0)
- gitlab_git (~> 10.2)
+ gitlab_git (~> 10.3.2)
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1)
- gollum-lib (~> 4.1.0)
+ gollum-lib (~> 4.2)
gollum-rugged_adapter (~> 0.4.2)
gon (~> 6.0.1)
grape (~> 0.13.0)
grape-entity (~> 0.4.2)
- haml-rails (~> 0.9.0)
- health_check (~> 1.5.1)
+ hamlit (~> 2.5)
+ health_check (~> 2.1.0)
hipchat (~> 1.5.0)
html-pipeline (~> 1.11.0)
httparty (~> 0.13.3)
@@ -894,12 +884,12 @@ DEPENDENCIES
jquery-ui-rails (~> 5.0.0)
jwt
kaminari (~> 0.17.0)
- knapsack
+ knapsack (~> 1.11.0)
letter_opener_web (~> 1.3.0)
- license_finder
+ license_finder (~> 2.1.0)
licensee (~> 8.0.0)
loofah (~> 2.0.3)
- mail_room (~> 0.7)
+ mail_room (~> 0.8)
method_source (~> 0.8)
minitest (~> 5.7.0)
mousetrap-rails (~> 1.4.6)
@@ -908,7 +898,7 @@ DEPENDENCIES
net-ssh (~> 3.0.1)
newrelic_rpm (~> 3.14)
nokogiri (~> 1.6.7, >= 1.6.7.2)
- oauth2 (~> 1.0.0)
+ oauth2 (~> 1.2.0)
octokit (~> 4.3.0)
omniauth (~> 1.3.1)
omniauth-auth0 (~> 1.4.1)
@@ -918,9 +908,9 @@ DEPENDENCIES
omniauth-facebook (~> 3.0.0)
omniauth-github (~> 1.1.1)
omniauth-gitlab (~> 1.0.0)
- omniauth-google-oauth2 (~> 0.2.0)
+ omniauth-google-oauth2 (~> 0.4.1)
omniauth-kerberos (~> 0.3.0)
- omniauth-saml (~> 1.5.0)
+ omniauth-saml (~> 1.6.0)
omniauth-shibboleth (~> 1.2.0)
omniauth-twitter (~> 1.2.0)
omniauth_crowd (~> 2.2.0)
@@ -929,40 +919,40 @@ DEPENDENCIES
pg (~> 0.18.2)
poltergeist (~> 1.9.0)
premailer-rails (~> 1.9.0)
- pry-rails
- quiet_assets (~> 1.0.2)
+ pry-rails (~> 0.3.4)
rack-attack (~> 4.3.1)
rack-cors (~> 0.4.0)
rack-oauth2 (~> 1.2.1)
- rails (= 4.2.6)
+ rails (= 4.2.7)
rails-deprecated_sanitizer (~> 1.0.3)
rainbow (~> 2.1.0)
- rblineprof
+ rblineprof (~> 0.3.6)
rdoc (~> 3.6)
recaptcha (~> 3.0)
redcarpet (~> 3.3.3)
redis (~> 3.2)
- redis-namespace
+ redis-namespace (~> 1.5.2)
redis-rails (~> 4.0.0)
request_store (~> 1.3.0)
rerun (~> 0.11.0)
responders (~> 2.0)
- rouge (~> 1.11)
+ rouge (~> 2.0)
rqrcode-rails3 (~> 0.1.7)
- rspec-rails (~> 3.4.0)
- rspec-retry
- rubocop (~> 0.40.0)
+ rspec-rails (~> 3.5.0)
+ rspec-retry (~> 0.4.5)
+ rubocop (~> 0.41.2)
rubocop-rspec (~> 1.5.0)
ruby-fogbugz (~> 0.2.1)
sanitize (~> 2.0)
sass-rails (~> 5.0.0)
scss_lint (~> 0.47.0)
sdoc (~> 0.3.20)
+ secure_headers (~> 3.3)
seed-fu (~> 2.3.5)
select2-rails (~> 3.5.9)
- sentry-raven (~> 0.15)
+ sentry-raven (~> 1.1.0)
settingslogic (~> 2.0.9)
- sham_rack
+ sham_rack (~> 1.3.6)
shoulda-matchers (~> 2.8.0)
sidekiq (~> 4.0)
sidekiq-cron (~> 0.4.0)
@@ -978,11 +968,12 @@ DEPENDENCIES
spring-commands-teaspoon (~> 0.0.2)
sprockets (~> 3.6.0)
state_machines-activerecord (~> 0.4.0)
+ sys-filesystem (~> 1.1.6)
task_list (~> 1.0.2)
teaspoon (~> 1.1.0)
teaspoon-jasmine (~> 2.2.0)
test_after_commit (~> 0.4.2)
- thin (~> 1.6.1)
+ thin (~> 1.7.0)
tinder (~> 1.10.0)
turbolinks (~> 2.5.0)
u2f (~> 0.2.1)
@@ -993,6 +984,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/MAINTENANCE.md b/MAINTENANCE.md
index d3d36670693..1efb2a35f6d 100644
--- a/MAINTENANCE.md
+++ b/MAINTENANCE.md
@@ -1,15 +1,35 @@
# GitLab Maintenance Policy
-GitLab is a fast moving and evolving project. We currently don't have the resources to support many releases concurrently. We support exactly one stable release at any given time.
+GitLab follows the [Semantic Versioning](http://semver.org/) for its releases:
+`(Major).(Minor).(Patch)` in a [pragmatic way].
-GitLab follows the [Semantic Versioning](http://semver.org/) for its releases: `(Major).(Minor).(Patch)` in a [pragmatic way](https://gist.github.com/jashkenas/cbd2b088e20279ae2c8e).
+- **Major version**: Whenever there is something significant or any backwards
+ incompatible changes are introduced to the public API.
+- **Minor version**: When new, backwards compatible functionality is introduced
+ to the public API or a minor feature is introduced, or when a set of smaller
+ features is rolled out.
+- **Patch number**: When backwards compatible bug fixes are introduced that fix
+ incorrect behavior.
-- **Major version**: Whenever there is something significant or any backwards incompatible changes are introduced to the public API.
-- **Minor version**: When new, backwards compatible functionality is introduced to the public API or a minor feature is introduced, or when a set of smaller features is rolled out.
-- **Patch number**: When backwards compatible bug fixes are introduced that fix incorrect behavior.
+The current stable release will receive security patches and bug fixes
+(eg. `8.9.0` -> `8.9.1`). Feature releases will mark the next supported stable
+release where the minor version is increased numerically by increments of one
+(eg. `8.9 -> 8.10`).
-The current stable release will receive security patches and bug fixes (eg. `5.0` -> `5.0.1`). Feature releases will mark the next supported stable release where the minor version is increased numerically by increments of one (eg. `5.0 -> 5.1`).
+Our current policy is to support one stable release at any given time, but for
+medium-level security issues, we may consider [backporting to the previous two
+monthly releases][rel-sec].
-We encourage everyone to run the latest stable release to ensure that you can easily upgrade to the most secure and feature rich GitLab experience. In order to make sure you can easily run the most recent stable release, we are working hard to keep the update process simple and reliable.
+We encourage everyone to run the latest stable release to ensure that you can
+easily upgrade to the most secure and feature-rich GitLab experience. In order
+to make sure you can easily run the most recent stable release, we are working
+hard to keep the update process simple and reliable.
-More information about the release procedures can be found in the doc/release directory.
+More information about the release procedures can be found in our
+[release-tools documentation][rel]. You may also want to read our
+[Responsible Disclosure Policy][disclosure].
+
+[rel-sec]: https://gitlab.com/gitlab-org/release-tools/blob/master/doc/security.md#backporting
+[rel]: https://gitlab.com/gitlab-org/release-tools/blob/master/doc/
+[disclosure]: https://about.gitlab.com/disclosure/
+[pragmatic way]: https://gist.github.com/jashkenas/cbd2b088e20279ae2c8e
diff --git a/VERSION b/VERSION
index 6c07f656285..213504430f3 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-8.9.0-pre
+8.10.0-pre
diff --git a/app/assets/images/auth_buttons/azure_64.png b/app/assets/images/auth_buttons/azure_64.png
index a82c751e001..85de7793440 100644
--- a/app/assets/images/auth_buttons/azure_64.png
+++ b/app/assets/images/auth_buttons/azure_64.png
Binary files differ
diff --git a/app/assets/images/auth_buttons/bitbucket_64.png b/app/assets/images/auth_buttons/bitbucket_64.png
index 4b90a57bc7d..b3d022a5a70 100644
--- a/app/assets/images/auth_buttons/bitbucket_64.png
+++ b/app/assets/images/auth_buttons/bitbucket_64.png
Binary files differ
diff --git a/app/assets/images/auth_buttons/facebook_64.png b/app/assets/images/auth_buttons/facebook_64.png
index 1f1a80d7368..71ffb1c6a1f 100644
--- a/app/assets/images/auth_buttons/facebook_64.png
+++ b/app/assets/images/auth_buttons/facebook_64.png
Binary files differ
diff --git a/app/assets/images/auth_buttons/github_64.png b/app/assets/images/auth_buttons/github_64.png
index 182a1a3f734..1fa19c55d2f 100644
--- a/app/assets/images/auth_buttons/github_64.png
+++ b/app/assets/images/auth_buttons/github_64.png
Binary files differ
diff --git a/app/assets/images/auth_buttons/gitlab_64.png b/app/assets/images/auth_buttons/gitlab_64.png
index 99a40583b3a..f675678dc9d 100644
--- a/app/assets/images/auth_buttons/gitlab_64.png
+++ b/app/assets/images/auth_buttons/gitlab_64.png
Binary files differ
diff --git a/app/assets/images/auth_buttons/google_64.png b/app/assets/images/auth_buttons/google_64.png
index fb64f8bee68..720824230a5 100644
--- a/app/assets/images/auth_buttons/google_64.png
+++ b/app/assets/images/auth_buttons/google_64.png
Binary files differ
diff --git a/app/assets/images/auth_buttons/twitter_64.png b/app/assets/images/auth_buttons/twitter_64.png
index e3bd9169a34..a4f14de57ae 100644
--- a/app/assets/images/auth_buttons/twitter_64.png
+++ b/app/assets/images/auth_buttons/twitter_64.png
Binary files differ
diff --git a/app/assets/images/bg_fallback.png b/app/assets/images/bg_fallback.png
index e5fe659ba63..5c55bc79dec 100644
--- a/app/assets/images/bg_fallback.png
+++ b/app/assets/images/bg_fallback.png
Binary files differ
diff --git a/app/assets/images/dark-scheme-preview.png b/app/assets/images/dark-scheme-preview.png
index 2ef58e52549..8855babf147 100644
--- a/app/assets/images/dark-scheme-preview.png
+++ b/app/assets/images/dark-scheme-preview.png
Binary files differ
diff --git a/app/assets/images/emoji.png b/app/assets/images/emoji.png
index 1e7cf79ea45..6f1a34a5591 100644
--- a/app/assets/images/emoji.png
+++ b/app/assets/images/emoji.png
Binary files differ
diff --git a/app/assets/images/emoji@2x.png b/app/assets/images/emoji@2x.png
index 74d67f7520d..dc9cae1d44c 100644
--- a/app/assets/images/emoji@2x.png
+++ b/app/assets/images/emoji@2x.png
Binary files differ
diff --git a/app/assets/images/gitlab_logo.png b/app/assets/images/gitlab_logo.png
index 0c157546b9c..ca30b459019 100644
--- a/app/assets/images/gitlab_logo.png
+++ b/app/assets/images/gitlab_logo.png
Binary files differ
diff --git a/app/assets/images/gitorious-logo-black.png b/app/assets/images/gitorious-logo-black.png
index 78f17a9af79..4a55fdc225a 100644
--- a/app/assets/images/gitorious-logo-black.png
+++ b/app/assets/images/gitorious-logo-black.png
Binary files differ
diff --git a/app/assets/images/gitorious-logo-blue.png b/app/assets/images/gitorious-logo-blue.png
index 4962cffba31..5eaa327d3df 100644
--- a/app/assets/images/gitorious-logo-blue.png
+++ b/app/assets/images/gitorious-logo-blue.png
Binary files differ
diff --git a/app/assets/images/icon-link.png b/app/assets/images/icon-link.png
index 7d89da97e11..5b55e12571c 100644
--- a/app/assets/images/icon-link.png
+++ b/app/assets/images/icon-link.png
Binary files differ
diff --git a/app/assets/images/images.png b/app/assets/images/images.png
index ad146246caf..bd60de994c4 100644
--- a/app/assets/images/images.png
+++ b/app/assets/images/images.png
Binary files differ
diff --git a/app/assets/images/monokai-scheme-preview.png b/app/assets/images/monokai-scheme-preview.png
index fbb339c6a91..d9c7d2d8041 100644
--- a/app/assets/images/monokai-scheme-preview.png
+++ b/app/assets/images/monokai-scheme-preview.png
Binary files differ
diff --git a/app/assets/images/msapplication-tile.png b/app/assets/images/msapplication-tile.png
index 58bbf2b20cb..1e0e2ed73ce 100644
--- a/app/assets/images/msapplication-tile.png
+++ b/app/assets/images/msapplication-tile.png
Binary files differ
diff --git a/app/assets/images/no_avatar.png b/app/assets/images/no_avatar.png
index 8287acbce13..5383d687b53 100644
--- a/app/assets/images/no_avatar.png
+++ b/app/assets/images/no_avatar.png
Binary files differ
diff --git a/app/assets/images/no_group_avatar.png b/app/assets/images/no_group_avatar.png
index bfb31bb2184..71612d55286 100644
--- a/app/assets/images/no_group_avatar.png
+++ b/app/assets/images/no_group_avatar.png
Binary files differ
diff --git a/app/assets/images/slider_handles.png b/app/assets/images/slider_handles.png
index 884378ec96a..52ad11ab7a1 100644
--- a/app/assets/images/slider_handles.png
+++ b/app/assets/images/slider_handles.png
Binary files differ
diff --git a/app/assets/images/touch-icon-ipad-retina.png b/app/assets/images/touch-icon-ipad-retina.png
index feb32b48ec9..516dc2f4710 100644
--- a/app/assets/images/touch-icon-ipad-retina.png
+++ b/app/assets/images/touch-icon-ipad-retina.png
Binary files differ
diff --git a/app/assets/images/touch-icon-ipad.png b/app/assets/images/touch-icon-ipad.png
index a6ddc543509..b2093d015b8 100644
--- a/app/assets/images/touch-icon-ipad.png
+++ b/app/assets/images/touch-icon-ipad.png
Binary files differ
diff --git a/app/assets/images/touch-icon-iphone-retina.png b/app/assets/images/touch-icon-iphone-retina.png
index 8bf7ccb7534..438654e0d20 100644
--- a/app/assets/images/touch-icon-iphone-retina.png
+++ b/app/assets/images/touch-icon-iphone-retina.png
Binary files differ
diff --git a/app/assets/images/touch-icon-iphone.png b/app/assets/images/touch-icon-iphone.png
index 87da550f8be..e5f87fbbcf6 100644
--- a/app/assets/images/touch-icon-iphone.png
+++ b/app/assets/images/touch-icon-iphone.png
Binary files differ
diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee
index 3f61ea1eaf4..89b0ac697ed 100644
--- a/app/assets/javascripts/api.js.coffee
+++ b/app/assets/javascripts/api.js.coffee
@@ -3,10 +3,11 @@
groupPath: "/api/:version/groups/:id.json"
namespacesPath: "/api/:version/namespaces.json"
groupProjectsPath: "/api/:version/groups/:id/projects.json"
- projectsPath: "/api/:version/projects.json"
+ projectsPath: "/api/:version/projects.json?simple=true"
labelsPath: "/api/:version/projects/:id/labels"
licensePath: "/api/:version/licenses/:key"
gitignorePath: "/api/:version/gitignores/:key"
+ gitlabCiYmlPath: "/api/:version/gitlab_ci_ymls/:key"
group: (group_id, callback) ->
url = Api.buildUrl(Api.groupPath)
@@ -110,6 +111,12 @@
$.get url, (gitignore) ->
callback(gitignore)
+ gitlabCiYml: (key, callback) ->
+ url = Api.buildUrl(Api.gitlabCiYmlPath).replace(':key', key)
+
+ $.get url, (file) ->
+ callback(file)
+
buildUrl: (url) ->
url = gon.relative_url_root + url if gon.relative_url_root?
return url.replace(':version', gon.api_version)
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index 2f9f6c3ef5b..eceff6d91d5 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -47,15 +47,12 @@
#= require date.format
#= require_directory ./behaviors
#= require_directory ./blob
-#= require_directory ./ci
#= require_directory ./commit
#= require_directory ./extensions
-#= require_directory ./lib
+#= require_directory ./lib/utils
#= require_directory ./u2f
#= require_directory .
#= require fuzzaldrin-plus
-#= require cropper
-#= require u2f
window.slugify = (text) ->
text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase()
@@ -121,6 +118,11 @@ window.onload = ->
setTimeout shiftWindow, 100
$ ->
+
+ $document = $(document)
+ $window = $(window)
+ $body = $('body')
+
gl.utils.preventDisabledButtons()
bootstrapBreakpoint = bp.getBreakpointSize()
@@ -152,7 +154,7 @@ $ ->
), 1
# Initialize tooltips
- $('body').tooltip(
+ $body.tooltip(
selector: '.has-tooltip, [data-toggle="tooltip"]'
placement: (_, el) ->
$el = $(el)
@@ -171,7 +173,7 @@ $ ->
flash.show()
# Disable form buttons while a form is submitting
- $('body').on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) ->
+ $body.on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) ->
buttons = $('[type="submit"]', @)
switch e.type
@@ -180,11 +182,20 @@ $ ->
else
buttons.enable()
+ $(document).ajaxError (e, xhrObj, xhrSetting, xhrErrorText) ->
+
+ if xhrObj.status is 401
+ new Flash 'You need to be logged in.', 'alert'
+
+ else if xhrObj.status in [ 404, 500 ]
+ new Flash 'Something went wrong on our end.', 'alert'
+
+
# Show/Hide the profile menu when hovering the account box
$('.account-box').hover -> $(@).toggleClass('hover')
# Commit show suppressed diff
- $(document).on 'click', '.diff-content .js-show-suppressed-diff', ->
+ $document.on 'click', '.diff-content .js-show-suppressed-diff', ->
$container = $(@).parent()
$container.next('table').show()
$container.remove()
@@ -194,16 +205,15 @@ $ ->
$('.header-content .header-logo').toggle()
$('.header-content .navbar-collapse').toggle()
$('.navbar-toggle').toggleClass('active')
- $('.navbar-toggle i').toggleClass("fa-angle-right fa-angle-left")
# Show/hide comments on diff
- $("body").on "click", ".js-toggle-diff-comments", (e) ->
+ $body.on "click", ".js-toggle-diff-comments", (e) ->
$(@).toggleClass('active')
$(@).closest(".diff-file").find(".notes_holder").toggle()
e.preventDefault()
- $(document).off "click", '.js-confirm-danger'
- $(document).on "click", '.js-confirm-danger', (e) ->
+ $document.off "click", '.js-confirm-danger'
+ $document.on "click", '.js-confirm-danger', (e) ->
e.preventDefault()
btn = $(e.target)
text = btn.data("confirm-danger-message")
@@ -211,7 +221,7 @@ $ ->
new ConfirmDangerModal(form, text)
- $(document).on 'click', 'button', ->
+ $document.on 'click', 'button', ->
$(this).blur()
$('input[type="search"]').each ->
@@ -219,7 +229,7 @@ $ ->
$this.attr 'value', $this.val()
return
- $(document)
+ $document
.off 'keyup', 'input[type="search"]'
.on 'keyup', 'input[type="search"]' , (e) ->
$this = $(this)
@@ -227,7 +237,7 @@ $ ->
$sidebarGutterToggle = $('.js-sidebar-toggle')
- $(document)
+ $document
.off 'breakpoint:change'
.on 'breakpoint:change', (e, breakpoint) ->
if breakpoint is 'sm' or breakpoint is 'xs'
@@ -239,14 +249,14 @@ $ ->
oldBootstrapBreakpoint = bootstrapBreakpoint
bootstrapBreakpoint = bp.getBreakpointSize()
if bootstrapBreakpoint != oldBootstrapBreakpoint
- $(document).trigger('breakpoint:change', [bootstrapBreakpoint])
+ $document.trigger('breakpoint:change', [bootstrapBreakpoint])
checkInitialSidebarSize = ->
bootstrapBreakpoint = bp.getBreakpointSize()
if bootstrapBreakpoint is "xs" or "sm"
- $(document).trigger('breakpoint:change', [bootstrapBreakpoint])
+ $document.trigger('breakpoint:change', [bootstrapBreakpoint])
- $(window)
+ $window
.off "resize.app"
.on "resize.app", (e) ->
fitSidebarForSize()
@@ -256,29 +266,45 @@ $ ->
new Aside()
# Sidenav pinning
- if $(window).width() < 1440 and $.cookie('pin_nav') is 'true'
- $.cookie('pin_nav', 'false')
+ if $window.width() < 1024 and $.cookie('pin_nav') is 'true'
+ $.cookie('pin_nav', 'false', { path: '/', expires: 365 * 10 })
$('.page-with-sidebar')
.toggleClass('page-sidebar-collapsed page-sidebar-expanded')
.removeClass('page-sidebar-pinned')
$('.navbar-fixed-top').removeClass('header-pinned-nav')
- $(document)
+ $document
.off 'click', '.js-nav-pin'
.on 'click', '.js-nav-pin', (e) ->
e.preventDefault()
+ $pinBtn = $(e.currentTarget)
+ $page = $ '.page-with-sidebar'
+ $topNav = $ '.navbar-fixed-top'
+ $tooltip = $ "##{$pinBtn.attr('aria-describedby')}"
+ doPinNav = not $page.is('.page-sidebar-pinned')
+ tooltipText = 'Pin navigation'
+
$(this).toggleClass 'is-active'
- if $.cookie('pin_nav') is 'true'
- $.cookie 'pin_nav', 'false'
- $('.page-with-sidebar')
- .removeClass('page-sidebar-pinned')
- .toggleClass('page-sidebar-collapsed page-sidebar-expanded')
- $('.navbar-fixed-top')
- .removeClass('header-pinned-nav')
- .toggleClass('header-collapsed header-expanded')
+ if doPinNav
+ $page.addClass('page-sidebar-pinned')
+ $topNav.addClass('header-pinned-nav')
else
- $.cookie 'pin_nav', 'true'
- $('.page-with-sidebar').addClass('page-sidebar-pinned')
- $('.navbar-fixed-top').addClass('header-pinned-nav')
+ $tooltip.remove() # Remove it immediately when collapsing the sidebar
+ $page.removeClass('page-sidebar-pinned')
+ .toggleClass('page-sidebar-collapsed page-sidebar-expanded')
+ $topNav.removeClass('header-pinned-nav')
+ .toggleClass('header-collapsed header-expanded')
+
+ # Save settings
+ $.cookie 'pin_nav', doPinNav, { path: '/', expires: 365 * 10 }
+
+ if $.cookie('pin_nav') is 'true' or doPinNav
+ tooltipText = 'Unpin navigation'
+
+ # Update tooltip text immediately
+ $tooltip.find('.tooltip-inner').text(tooltipText)
+
+ # Persist tooltip title
+ $pinBtn.attr('title', tooltipText).tooltip('fixTitle')
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/blob/blob_ci_yaml.js.coffee b/app/assets/javascripts/blob/blob_ci_yaml.js.coffee
new file mode 100644
index 00000000000..d9a03d05529
--- /dev/null
+++ b/app/assets/javascripts/blob/blob_ci_yaml.js.coffee
@@ -0,0 +1,23 @@
+#= require blob/template_selector
+
+class @BlobCiYamlSelector extends TemplateSelector
+ requestFile: (query) ->
+ Api.gitlabCiYml query.name, @requestFileSuccess.bind(@)
+
+class @BlobCiYamlSelectors
+ constructor: (opts) ->
+ {
+ @$dropdowns = $('.js-gitlab-ci-yml-selector')
+ @editor
+ } = opts
+
+ @$dropdowns.each (i, dropdown) =>
+ $dropdown = $(dropdown)
+
+ new BlobCiYamlSelector(
+ pattern: /(.gitlab-ci.yml)/,
+ data: $dropdown.data('data'),
+ wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'),
+ dropdown: $dropdown,
+ editor: @editor
+ )
diff --git a/app/assets/javascripts/blob/edit_blob.js.coffee b/app/assets/javascripts/blob/edit_blob.js.coffee
index 636f909dbd0..19e584519d7 100644
--- a/app/assets/javascripts/blob/edit_blob.js.coffee
+++ b/app/assets/javascripts/blob/edit_blob.js.coffee
@@ -15,6 +15,7 @@ class @EditBlob
new BlobLicenseSelectors { @editor }
new BlobGitignoreSelectors { @editor }
+ new BlobCiYamlSelectors { @editor }
initModePanesAndLinks: ->
@$editModePanes = $(".js-edit-mode-pane")
diff --git a/app/assets/javascripts/blob/template_selector.js.coffee b/app/assets/javascripts/blob/template_selector.js.coffee
index e76e303189d..40c9169beac 100644
--- a/app/assets/javascripts/blob/template_selector.js.coffee
+++ b/app/assets/javascripts/blob/template_selector.js.coffee
@@ -19,6 +19,7 @@ class @TemplateSelector
data: @data,
filterable: true,
selectable: true,
+ toggleLabel: @toggleLabel,
search:
fields: ['name']
clicked: @onClick
@@ -31,6 +32,9 @@ class @TemplateSelector
@onFilenameUpdate()
)
+ toggleLabel: (item) ->
+ item.name
+
onFilenameUpdate: ->
return unless @$input.length
diff --git a/app/assets/javascripts/ci/build.coffee b/app/assets/javascripts/build.coffee
index 2d515d7efa2..cf203ea43a0 100644
--- a/app/assets/javascripts/ci/build.coffee
+++ b/app/assets/javascripts/build.coffee
@@ -1,9 +1,9 @@
-class @CiBuild
+class @Build
@interval: null
@state: null
- constructor: (@build_url, @build_status, @state) ->
- clearInterval(CiBuild.interval)
+ constructor: (@page_url, @build_url, @build_status, @state) ->
+ clearInterval(Build.interval)
# Init breakpoint checker
@bp = Breakpoints.get()
@@ -40,8 +40,8 @@ class @CiBuild
# Check for new build output if user still watching build page
# Only valid for runnig build when output changes during time
#
- CiBuild.interval = setInterval =>
- if window.location.href.split("#").first() is @build_url
+ Build.interval = setInterval =>
+ if window.location.href.split("#").first() is @page_url
@getBuildTrace()
, 4000
@@ -57,7 +57,7 @@ class @CiBuild
getBuildTrace: ->
$.ajax
- url: "#{@build_url}/trace.json?state=#{encodeURIComponent(@state)}"
+ url: "#{@page_url}/trace.json?state=#{encodeURIComponent(@state)}"
dataType: "json"
success: (log) =>
if log.state
@@ -70,7 +70,7 @@ class @CiBuild
$('.js-build-output').html log.html
@checkAutoscroll()
else if log.status isnt @build_status
- Turbolinks.visit @build_url
+ Turbolinks.visit @page_url
checkAutoscroll: ->
$("html,body").scrollTop $("#build-trace").height() if "enabled" is $("#autoscroll-button").data("state")
diff --git a/app/assets/javascripts/ci/application.js.coffee b/app/assets/javascripts/ci/application.js.coffee
deleted file mode 100644
index ca24c1d759f..00000000000
--- a/app/assets/javascripts/ci/application.js.coffee
+++ /dev/null
@@ -1,12 +0,0 @@
-#= require pager
-#= require jquery_nested_form
-#= require_tree .
-
-$(document).on 'click', '.assign-all-runner', ->
- $(this).replaceWith('<i class="fa fa-refresh fa-spin"></i> Assign in progress..')
-
-window.unbindEvents = ->
- $(document).unbind('scroll')
- $(document).off('scroll')
-
-document.addEventListener("page:fetch", unbindEvents)
diff --git a/app/assets/javascripts/ci/projects.js.coffee b/app/assets/javascripts/ci/projects.js.coffee
deleted file mode 100644
index e6406011d11..00000000000
--- a/app/assets/javascripts/ci/projects.js.coffee
+++ /dev/null
@@ -1,3 +0,0 @@
-$(document).on 'click', '.badge-codes-toggle', ->
- $('.badge-codes-block').toggleClass("hide")
- return false
diff --git a/app/assets/javascripts/compare_autocomplete.js.coffee b/app/assets/javascripts/compare_autocomplete.js.coffee
new file mode 100644
index 00000000000..7ad9fd97637
--- /dev/null
+++ b/app/assets/javascripts/compare_autocomplete.js.coffee
@@ -0,0 +1,41 @@
+class @CompareAutocomplete
+ constructor: ->
+ @initDropdown()
+
+ initDropdown: ->
+ $('.js-compare-dropdown').each ->
+ $dropdown = $(@)
+ selected = $dropdown.data('selected')
+
+ $dropdown.glDropdown(
+ data: (term, callback) ->
+ $.ajax(
+ url: $dropdown.data('refs-url')
+ data:
+ ref: $dropdown.data('ref')
+ ).done (refs) ->
+ callback(refs)
+ selectable: true
+ filterable: true
+ filterByText: true
+ fieldName: $dropdown.attr('name')
+ filterInput: 'input[type="text"]'
+ renderRow: (ref) ->
+ if ref.header?
+ $('<li />')
+ .addClass('dropdown-header')
+ .text(ref.header)
+ else
+ link = $('<a />')
+ .attr('href', '#')
+ .addClass(if ref is selected then 'is-active' else '')
+ .text(ref)
+ .attr('data-ref', escape(ref))
+
+ $('<li />')
+ .append(link)
+ id: (obj, $el) ->
+ $el.attr('data-ref')
+ toggleLabel: (obj, $el) ->
+ $el.text().trim()
+ )
diff --git a/app/assets/javascripts/diff.js.coffee b/app/assets/javascripts/diff.js.coffee
index 6d9b364cb8d..c132cc8c542 100644
--- a/app/assets/javascripts/diff.js.coffee
+++ b/app/assets/javascripts/diff.js.coffee
@@ -1,6 +1,9 @@
class @Diff
UNFOLD_COUNT = 20
constructor: ->
+ $('.files .diff-file').singleFileDiff()
+ @filesCommentButton = $('.files .diff-file').filesCommentButton()
+
$(document).off('click', '.js-unfold')
$(document).on('click', '.js-unfold', (event) =>
target = $(event.target)
@@ -36,7 +39,7 @@ class @Diff
# see https://gitlab.com/gitlab-org/gitlab-ce/issues/707
indent: 1
- $.get(link, params, (response) =>
+ $.get(link, params, (response) ->
target.parent().replaceWith(response)
)
)
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index 7fbff9214cf..afaa6407b05 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,17 +127,18 @@ class Dispatcher
when 'groups'
new UsersSelect()
when 'projects'
- new NamespaceSelect()
+ new NamespaceSelects()
when 'dashboard', 'root'
shortcut_handler = new ShortcutsDashboardNavigation()
when 'profiles'
- new Profile()
new NotificationsForm()
new NotificationsDropdown()
when 'projects'
new Project()
new ProjectAvatar()
switch path[1]
+ when 'compare'
+ new CompareAutocomplete()
when 'edit'
shortcut_handler = new ShortcutsNavigation()
new ProjectNew()
diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee
index e2194589b38..665246e2a7d 100644
--- a/app/assets/javascripts/dropzone_input.js.coffee
+++ b/app/assets/javascripts/dropzone_input.js.coffee
@@ -70,12 +70,12 @@ class @DropzoneInput
pasteText response.link.markdown
return
- error: (temp, errorMessage) ->
+ error: (temp) ->
errorAlert = $(form).find('.error-alert')
checkIfMsgExists = errorAlert.children().length
if checkIfMsgExists is 0
errorAlert.append divAlert
- $(".div-dropzone-alert").append btnAlert + errorMessage
+ $(".div-dropzone-alert").append "#{btnAlert}Attaching the file failed."
return
totaluploadprogress: (totalUploadProgress) ->
diff --git a/app/assets/javascripts/files_comment_button.js.coffee b/app/assets/javascripts/files_comment_button.js.coffee
new file mode 100644
index 00000000000..db0bf7082a9
--- /dev/null
+++ b/app/assets/javascripts/files_comment_button.js.coffee
@@ -0,0 +1,97 @@
+class @FilesCommentButton
+ COMMENT_BUTTON_CLASS = '.add-diff-note'
+ COMMENT_BUTTON_TEMPLATE = _.template '<button name="button" type="submit" class="btn <%- COMMENT_BUTTON_CLASS %> js-add-diff-note-button" title="Add a comment to this line"><i class="fa fa-comment-o"></i></button>'
+ LINE_HOLDER_CLASS = '.line_holder'
+ LINE_NUMBER_CLASS = 'diff-line-num'
+ LINE_CONTENT_CLASS = 'line_content'
+ UNFOLDABLE_LINE_CLASS = 'js-unfold'
+ EMPTY_CELL_CLASS = 'empty-cell'
+ OLD_LINE_CLASS = 'old_line'
+ NEW_CLASS = 'new'
+ LINE_COLUMN_CLASSES = ".#{LINE_NUMBER_CLASS}, .line_content"
+ TEXT_FILE_SELECTOR = '.text-file'
+ DEBOUNCE_TIMEOUT_DURATION = 100
+
+ constructor: (@filesContainerElement) ->
+ @VIEW_TYPE = $('input#view[type=hidden]').val()
+
+ debounce = _.debounce @render, DEBOUNCE_TIMEOUT_DURATION
+
+ $(document)
+ .on 'mouseover', LINE_COLUMN_CLASSES, debounce
+ .on 'mouseleave', LINE_COLUMN_CLASSES, @destroy
+
+ render: (e) =>
+ $currentTarget = $(e.currentTarget)
+ buttonParentElement = @getButtonParent $currentTarget
+ return unless @shouldRender e, buttonParentElement
+
+ textFileElement = @getTextFileElement $currentTarget
+ lineContentElement = @getLineContent $currentTarget
+
+ buttonParentElement.append @buildButton
+ noteableType: textFileElement.attr 'data-noteable-type'
+ noteableID: textFileElement.attr 'data-noteable-id'
+ commitID: textFileElement.attr 'data-commit-id'
+ noteType: lineContentElement.attr 'data-note-type'
+ position: lineContentElement.attr 'data-position'
+ lineType: lineContentElement.attr 'data-line-type'
+ discussionID: lineContentElement.attr 'data-discussion-id'
+ lineCode: lineContentElement.attr 'data-line-code'
+ return
+
+ destroy: (e) =>
+ return if @isMovingToSameType e
+ $(COMMENT_BUTTON_CLASS, @getButtonParent $(e.currentTarget)).remove()
+ return
+
+ buildButton: (buttonAttributes) ->
+ initializedButtonTemplate = COMMENT_BUTTON_TEMPLATE
+ COMMENT_BUTTON_CLASS: COMMENT_BUTTON_CLASS.substr 1
+ $(initializedButtonTemplate).attr
+ 'data-noteable-type': buttonAttributes.noteableType
+ 'data-noteable-id': buttonAttributes.noteableID
+ 'data-commit-id': buttonAttributes.commitID
+ 'data-note-type': buttonAttributes.noteType
+ 'data-line-code': buttonAttributes.lineCode
+ 'data-position': buttonAttributes.position
+ 'data-discussion-id': buttonAttributes.discussionID
+ 'data-line-type': buttonAttributes.lineType
+
+ getTextFileElement: (hoveredElement) ->
+ $(hoveredElement.closest TEXT_FILE_SELECTOR)
+
+ getLineContent: (hoveredElement) ->
+ return hoveredElement if hoveredElement.hasClass LINE_CONTENT_CLASS
+
+ $(".#{LINE_CONTENT_CLASS + @diffTypeClass hoveredElement}", hoveredElement.parent())
+
+ getButtonParent: (hoveredElement) ->
+ if @VIEW_TYPE is 'inline'
+ return hoveredElement if hoveredElement.hasClass OLD_LINE_CLASS
+
+ $(".#{OLD_LINE_CLASS}", hoveredElement.parent())
+ else
+ return hoveredElement if hoveredElement.hasClass LINE_NUMBER_CLASS
+
+ $(".#{LINE_NUMBER_CLASS + @diffTypeClass hoveredElement}", hoveredElement.parent())
+
+ diffTypeClass: (hoveredElement) ->
+ if hoveredElement.hasClass(NEW_CLASS) then '.new' else '.old'
+
+ isMovingToSameType: (e) ->
+ newButtonParent = @getButtonParent $(e.toElement)
+ return false unless newButtonParent
+ newButtonParent.is @getButtonParent $(e.currentTarget)
+
+ shouldRender: (e, buttonParentElement) ->
+ (not buttonParentElement.hasClass(EMPTY_CELL_CLASS) and \
+ not buttonParentElement.hasClass(UNFOLDABLE_LINE_CLASS) and \
+ $(COMMENT_BUTTON_CLASS, buttonParentElement).length is 0)
+
+$.fn.filesCommentButton = ->
+ return unless this and @parent().data('can-create-note')?
+
+ @each ->
+ unless $.data this, 'filesCommentButton'
+ $.data this, 'filesCommentButton', new FilesCommentButton $(this)
diff --git a/app/assets/javascripts/flash.js.coffee b/app/assets/javascripts/flash.js.coffee
index 4f73d215b85..5a493041538 100644
--- a/app/assets/javascripts/flash.js.coffee
+++ b/app/assets/javascripts/flash.js.coffee
@@ -1,16 +1,28 @@
class @Flash
- constructor: (message, type = 'alert')->
- @flash = $(".flash-container")
- @flash.html("")
+ hideFlash = -> $(@).fadeOut()
- innerDiv = $('<div/>',
- class: "flash-#{type}",
+ constructor: (message, type = 'alert', parent = null)->
+ if parent
+ @flashContainer = parent.find('.flash-container')
+ else
+ @flashContainer = $('.flash-container-page')
+
+ @flashContainer.html('')
+
+ flash = $('<div/>',
+ class: "flash-#{type}"
+ )
+ flash.on 'click', hideFlash
+
+ textDiv = $('<div/>',
+ class: 'flash-text',
text: message
)
- innerDiv.appendTo(".flash-container")
+ textDiv.appendTo(flash)
+
+ if @flashContainer.parent().hasClass('content-wrapper')
+ textDiv.addClass('container-fluid container-limited')
- @flash.click -> $(@).fadeOut()
- @flash.show()
+ flash.appendTo(@flashContainer)
+ @flashContainer.show()
- pinTo: (selector) ->
- @flash.detach().appendTo(selector)
diff --git a/app/assets/javascripts/gfm_auto_complete.js.coffee b/app/assets/javascripts/gfm_auto_complete.js.coffee
index 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 2a8a1f05b35..951530e03a5 100644
--- a/app/assets/javascripts/gl_dropdown.js.coffee
+++ b/app/assets/javascripts/gl_dropdown.js.coffee
@@ -56,9 +56,10 @@ class GitLabDropdownFilter
return BLUR_KEYCODES.indexOf(keyCode) >= 0
filter: (search_text) ->
+ @options.onFilter(search_text) if @options.onFilter
data = @options.data()
- if data?
+ if data? and not @options.filterByText
results = data
if search_text isnt ''
@@ -102,10 +103,11 @@ class GitLabDropdownFilter
$el = $(@)
matches = fuzzaldrinPlus.match($el.text().trim(), search_text)
- if matches.length
- $el.show()
- else
- $el.hide()
+ unless $el.is('.dropdown-header')
+ if matches.length
+ $el.show()
+ else
+ $el.hide()
else
elements.show()
@@ -185,12 +187,16 @@ class GitLabDropdown
@fullData = data
@parseData @fullData
+
+ @filter.input.trigger('keyup') if @options.filterable and @filter and @filter.input
}
# Init filterable
if @options.filterable
@filter = new GitLabDropdownFilter @filterInput,
filterInputBlur: @filterInputBlur
+ filterByText: @options.filterByText
+ onFilter: @options.onFilter
remote: @options.filterRemote
query: @options.data
keys: searchFields
@@ -216,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) =>
@@ -237,6 +250,8 @@ class GitLabDropdown
if self.options.clicked
self.options.clicked(selected, $el, e)
+ $el.trigger('blur')
+
# Finds an element inside wrapper element
getElement: (selector) ->
@dropdown.find selector
@@ -278,7 +293,7 @@ class GitLabDropdown
html = @renderData(data)
# Render the full menu
- full_html = @renderMenu(html.join(""))
+ full_html = @renderMenu(html)
@appendMenu(full_html)
@@ -349,7 +364,8 @@ class GitLabDropdown
if @options.renderMenu
menu_html = @options.renderMenu(html)
else
- menu_html = "<ul>#{html}</ul>"
+ menu_html = $('<ul />')
+ .append(html)
return menu_html
@@ -358,7 +374,9 @@ class GitLabDropdown
selector = '.dropdown-content'
if @dropdown.find(".dropdown-toggle-page").length
selector = ".dropdown-page-one .dropdown-content"
- $(selector, @dropdown).html html
+ $(selector, @dropdown)
+ .empty()
+ .append(html)
# Render the row
renderItem: (data, group = false, index = false) ->
@@ -440,6 +458,8 @@ class GitLabDropdown
rowClicked: (el) ->
fieldName = @options.fieldName
+ isInput = $(@el).is('input')
+
if @renderedData
groupName = el.data('group')
if groupName
@@ -450,14 +470,23 @@ class GitLabDropdown
selectedObject = @renderedData[selectedIndex]
value = if @options.id then @options.id(selectedObject, el) else selectedObject.id
- field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']")
+
+ if isInput
+ field = $(@el)
+ else
+ field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']")
+
if el.hasClass(ACTIVE_CLASS)
el.removeClass(ACTIVE_CLASS)
- field.remove()
+
+ if isInput
+ field.val('')
+ else
+ field.remove()
# Toggle the dropdown label
if @options.toggleLabel
- @updateLabel()
+ @updateLabel(selectedObject, el, @)
else
selectedObject
else if el.hasClass(INDETERMINATE_CLASS)
@@ -474,7 +503,9 @@ class GitLabDropdown
else
if not @options.multiSelect or el.hasClass('dropdown-clear-active')
@dropdown.find(".#{ACTIVE_CLASS}").removeClass ACTIVE_CLASS
- @dropdown.parent().find("input[name='#{fieldName}']").remove()
+
+ unless isInput
+ @dropdown.parent().find("input[name='#{fieldName}']").remove()
if !value?
field.remove()
@@ -484,12 +515,14 @@ class GitLabDropdown
# Toggle the dropdown label
if @options.toggleLabel
- @updateLabel(selectedObject, el)
+ @updateLabel(selectedObject, el, @)
if value?
if !field.length and fieldName
@addInput(fieldName, value)
else
- field.val value
+ field
+ .val value
+ .trigger 'change'
return selectedObject
@@ -516,7 +549,7 @@ class GitLabDropdown
if $el.length
e.preventDefault()
e.stopImmediatePropagation()
- $(selector, @dropdown)[0].click()
+ $el.first().trigger('click')
addArrowKeyEvent: ->
ARROW_KEY_CODES = [38, 40]
@@ -583,8 +616,8 @@ class GitLabDropdown
# Scroll the dropdown content up
$dropdownContent.scrollTop(listItemTop - dropdownContentTop)
- updateLabel: (selected = null, el = null) =>
- $(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selected, el)
+ updateLabel: (selected = null, el = null, instance = null) =>
+ $(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selected, el, instance)
$.fn.glDropdown = (opts) ->
return @.each ->
diff --git a/app/assets/javascripts/gl_form.js.coffee b/app/assets/javascripts/gl_form.js.coffee
index d540cc4dc46..77512d187c9 100644
--- a/app/assets/javascripts/gl_form.js.coffee
+++ b/app/assets/javascripts/gl_form.js.coffee
@@ -34,6 +34,8 @@ class @GLForm
# form and textarea event listeners
@addEventListeners()
+ gl.text.init(@form)
+
# hide discard button
@form.find('.js-note-discard').hide()
@@ -42,6 +44,7 @@ class @GLForm
clearEventListeners: ->
@textarea.off 'focus'
@textarea.off 'blur'
+ gl.text.removeListeners(@form)
addEventListeners: ->
@textarea.on 'focus', ->
diff --git a/app/assets/javascripts/graphs/application.js.coffee b/app/assets/javascripts/graphs/application.js.coffee
index 91f81a5d249..e0f681acf0b 100644
--- a/app/assets/javascripts/graphs/application.js.coffee
+++ b/app/assets/javascripts/graphs/application.js.coffee
@@ -4,5 +4,4 @@
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
# the compiled file.
#
-#= require Chart
#= require_tree .
diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js.coffee b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js.coffee
index 584d281a510..834a81af459 100644
--- a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js.coffee
+++ b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js.coffee
@@ -121,7 +121,11 @@ class @ContributorsMasterGraph extends ContributorsGraph
class @ContributorsAuthorGraph extends ContributorsGraph
constructor: (@data) ->
- @width = $('.content').width()/2 - 100
+ # Don't split graph size in half for mobile devices.
+ if $(window).width() < 768
+ @width = $('.content').width() - 80
+ else
+ @width = ($('.content').width() / 2) - 100
@height = 200
@x = null
@y = null
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 d0901be1509..7f795f8096b 100644
--- a/app/assets/javascripts/issuable.js.coffee
+++ b/app/assets/javascripts/issuable.js.coffee
@@ -11,11 +11,11 @@ issuable_created = false
initTemplates: ->
Issuable.labelRow = _.template(
'<% _.each(labels, function(label){ %>
- <span class="label-row btn-group" role="group" aria-label="<%= _.escape(label.title) %>" style="color: <%= label.text_color %>;">
- <a href="#" class="btn btn-transparent has-tooltip" style="background-color: <%= label.color %>;" title="<%= _.escape(label.description) %>" data-container="body">
- <%= _.escape(label.title) %>
+ <span class="label-row btn-group" role="group" aria-label="<%- label.title %>" style="color: <%- label.text_color %>;">
+ <a href="#" class="btn btn-transparent has-tooltip" style="background-color: <%- label.color %>;" title="<%- label.description %>" data-container="body">
+ <%- label.title %>
</a>
- <button type="button" class="btn btn-transparent label-remove js-label-filter-remove" style="background-color: <%= label.color %>;" data-label="<%= _.escape(label.title) %>">
+ <button type="button" class="btn btn-transparent label-remove js-label-filter-remove" style="background-color: <%- label.color %>;" data-label="<%- label.title %>">
<i class="fa fa-times"></i>
</button>
</span>
@@ -32,13 +32,11 @@ issuable_created = false
$search = $('#issue_search')
$form = $('.js-filter-form')
$input = $("input[name='#{$search.attr('name')}']", $form)
-
if $input.length is 0
$form.append "<input type='hidden' name='#{$search.attr('name')}' value='#{_.escape($search.val())}'/>"
else
$input.val $search.val()
-
- Issuable.filterResults $form
+ Issuable.filterResults $form if $search.val() isnt ''
, 500)
initLabelFilterRemove: ->
@@ -59,21 +57,23 @@ 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')
+
$('.check_all_issues').off('click').on('click', ->
$('.selected_issue').prop('checked', @checked)
Issuable.checkChanged()
)
- $('.selected_issue').off('change').on('change', Issuable.checkChanged)
+ $('.selected_issue').off('change').on('change', Issuable.checkChanged.bind(@))
+
checkChanged: ->
checked_issues = $('.selected_issue:checked')
@@ -88,3 +88,6 @@ issuable_created = false
$('#update_issues_ids').val []
$('.issues_bulk_update').hide()
$('.issues-other-filters').show()
+ @issuableBulkActions.willUpdateLabels = false
+
+ return true
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/issue_status_select.js.coffee b/app/assets/javascripts/issue_status_select.js.coffee
index c5740f27ddd..ed50e2e698f 100644
--- a/app/assets/javascripts/issue_status_select.js.coffee
+++ b/app/assets/javascripts/issue_status_select.js.coffee
@@ -6,6 +6,13 @@ class @IssueStatusSelect
$(el).glDropdown(
selectable: true
fieldName: fieldName
+ toggleLabel: (selected, el, instance) =>
+ label = 'Author'
+ $item = instance.dropdown.find('.is-active')
+ label = $item.text() if $item.length
+ label
+ clicked: (item, $el, e)->
+ e.preventDefault()
id: (obj, el) ->
$(el).data("id")
)
diff --git a/app/assets/javascripts/issues-bulk-assignment.js.coffee b/app/assets/javascripts/issues-bulk-assignment.js.coffee
index b454f9389dd..3d09ea08e3b 100644
--- a/app/assets/javascripts/issues-bulk-assignment.js.coffee
+++ b/app/assets/javascripts/issues-bulk-assignment.js.coffee
@@ -7,6 +7,11 @@ class @IssuableBulkActions
@issues = @getElement('.issues-list .issue')
} = opts
+ # Save instance
+ @form.data 'bulkActions', @
+
+ @willUpdateLabels = false
+
@bindEvents()
# Fixes bulk-assign not working when navigating through pages
@@ -80,18 +85,20 @@ class @IssuableBulkActions
getFormDataAsObject: ->
formData =
update:
- state_event : @form.find('input[name="update[state_event]"]').val()
- assignee_id : @form.find('input[name="update[assignee_id]"]').val()
- milestone_id : @form.find('input[name="update[milestone_id]"]').val()
- issues_ids : @form.find('input[name="update[issues_ids]"]').val()
- add_label_ids : []
- remove_label_ids : []
-
- @getLabelsToApply().map (id) ->
- formData.update.add_label_ids.push id
-
- @getLabelsToRemove().map (id) ->
- formData.update.remove_label_ids.push id
+ state_event : @form.find('input[name="update[state_event]"]').val()
+ assignee_id : @form.find('input[name="update[assignee_id]"]').val()
+ milestone_id : @form.find('input[name="update[milestone_id]"]').val()
+ issues_ids : @form.find('input[name="update[issues_ids]"]').val()
+ subscription_event : @form.find('input[name="update[subscription_event]"]').val()
+ add_label_ids : []
+ remove_label_ids : []
+
+ if @willUpdateLabels
+ @getLabelsToApply().map (id) ->
+ formData.update.add_label_ids.push id
+
+ @getLabelsToRemove().map (id) ->
+ formData.update.remove_label_ids.push id
formData
diff --git a/app/assets/javascripts/labels_select.js.coffee b/app/assets/javascripts/labels_select.js.coffee
index 6a10db10eb1..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
@@ -319,6 +319,8 @@ class @LabelsSelect
multiSelect: $dropdown.hasClass 'js-multiselect'
clicked: (label) ->
+ _this.enableBulkLabelDropdown()
+
if $dropdown.hasClass('js-filter-bulk-update')
return
@@ -377,3 +379,8 @@ class @LabelsSelect
label_ids.push $("#issue_#{issue_id}").data('labels')
_.intersection.apply _, label_ids
+
+ enableBulkLabelDropdown: ->
+ if $('.selected_issue:checked').length
+ issuableBulkActions = $('.bulk-update').data('bulkActions')
+ issuableBulkActions.willUpdateLabels = true
diff --git a/app/assets/javascripts/layout_nav.js.coffee b/app/assets/javascripts/layout_nav.js.coffee
index f8f0aea427e..f639f7f5892 100644
--- a/app/assets/javascripts/layout_nav.js.coffee
+++ b/app/assets/javascripts/layout_nav.js.coffee
@@ -3,11 +3,10 @@ hideEndFade = ($scrollingTabs) ->
$this = $(@)
$this
- .find('.fade-right')
- .toggleClass('end-scroll', $this.width() is $this.prop('scrollWidth'))
+ .siblings('.fade-right')
+ .toggleClass('scrolling', $this.width() < $this.prop('scrollWidth'))
$ ->
- $('.fade-left').addClass('end-scroll')
hideEndFade($('.scrolling-tabs'))
@@ -21,5 +20,5 @@ $ ->
currentPosition = $this.scrollLeft()
maxPosition = $this.prop('scrollWidth') - $this.outerWidth()
- $this.find('.fade-left').toggleClass('end-scroll', currentPosition is 0)
- $this.find('.fade-right').toggleClass('end-scroll', currentPosition is maxPosition)
+ $this.siblings('.fade-left').toggleClass('scrolling', currentPosition > 0)
+ $this.siblings('.fade-right').toggleClass('scrolling', currentPosition < maxPosition - 1)
diff --git a/app/assets/javascripts/lib/chart.js.coffee b/app/assets/javascripts/lib/chart.js.coffee
new file mode 100644
index 00000000000..82217fc5107
--- /dev/null
+++ b/app/assets/javascripts/lib/chart.js.coffee
@@ -0,0 +1 @@
+#= require Chart
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/d3.js.coffee b/app/assets/javascripts/lib/d3.js.coffee
new file mode 100644
index 00000000000..74f0a0bb06a
--- /dev/null
+++ b/app/assets/javascripts/lib/d3.js.coffee
@@ -0,0 +1 @@
+#= require d3
diff --git a/app/assets/javascripts/lib/raphael.js.coffee b/app/assets/javascripts/lib/raphael.js.coffee
new file mode 100644
index 00000000000..ab8e5979b87
--- /dev/null
+++ b/app/assets/javascripts/lib/raphael.js.coffee
@@ -0,0 +1,3 @@
+#= require raphael
+#= require g.raphael
+#= require g.bar
diff --git a/app/assets/javascripts/lib/animate.js.coffee b/app/assets/javascripts/lib/utils/animate.js.coffee
index ec3b44d6126..ec3b44d6126 100644
--- a/app/assets/javascripts/lib/animate.js.coffee
+++ b/app/assets/javascripts/lib/utils/animate.js.coffee
diff --git a/app/assets/javascripts/lib/common_utils.js.coffee b/app/assets/javascripts/lib/utils/common_utils.js.coffee
index e39dcb2daa9..d4dd3dc329a 100644
--- a/app/assets/javascripts/lib/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/datetime_utility.js.coffee b/app/assets/javascripts/lib/utils/datetime_utility.js.coffee
index 948d6dbf07e..2371e913844 100644
--- a/app/assets/javascripts/lib/datetime_utility.js.coffee
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js.coffee
@@ -2,10 +2,14 @@
w.gl ?= {}
w.gl.utils ?= {}
+ w.gl.utils.days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
w.gl.utils.formatDate = (datetime) ->
dateFormat(datetime, 'mmm d, yyyy h:MMtt Z')
+ w.gl.utils.getDayName = (date) ->
+ this.days[date.getDay()]
+
w.gl.utils.localTimeAgo = ($timeagoEls, setTimeago = true) ->
$timeagoEls.each( ->
$el = $(@)
diff --git a/app/assets/javascripts/lib/emoji_aliases.js.coffee.erb b/app/assets/javascripts/lib/utils/emoji_aliases.js.coffee.erb
index 80f9936b9c2..80f9936b9c2 100644
--- a/app/assets/javascripts/lib/emoji_aliases.js.coffee.erb
+++ b/app/assets/javascripts/lib/utils/emoji_aliases.js.coffee.erb
diff --git a/app/assets/javascripts/lib/jquery.timeago.js b/app/assets/javascripts/lib/utils/jquery.timeago.js
index cc17aa7d3d1..cc17aa7d3d1 100644
--- a/app/assets/javascripts/lib/jquery.timeago.js
+++ b/app/assets/javascripts/lib/utils/jquery.timeago.js
diff --git a/app/assets/javascripts/lib/md5.js b/app/assets/javascripts/lib/utils/md5.js
index b63716eaad2..b63716eaad2 100644
--- a/app/assets/javascripts/lib/md5.js
+++ b/app/assets/javascripts/lib/utils/md5.js
diff --git a/app/assets/javascripts/lib/notify.js.coffee b/app/assets/javascripts/lib/utils/notify.js.coffee
index 9e28353ac34..9e28353ac34 100644
--- a/app/assets/javascripts/lib/notify.js.coffee
+++ b/app/assets/javascripts/lib/utils/notify.js.coffee
diff --git a/app/assets/javascripts/lib/utils/text_utility.js.coffee b/app/assets/javascripts/lib/utils/text_utility.js.coffee
new file mode 100644
index 00000000000..2e1407f8738
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/text_utility.js.coffee
@@ -0,0 +1,105 @@
+((w) ->
+ w.gl ?= {}
+ w.gl.text ?= {}
+
+ gl.text.randomString = -> Math.random().toString(36).substring(7)
+
+ gl.text.replaceRange = (s, start, end, substitute) ->
+ s.substring(0, start) + substitute + s.substring(end);
+
+ gl.text.selectedText = (text, textarea) ->
+ text.substring(textarea.selectionStart, textarea.selectionEnd)
+
+ 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 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')
+ inserted = document.execCommand 'insertText', false, insertText
+
+ unless inserted
+ try
+ document.execCommand("ms-beginUndoUnit")
+
+ textArea.value = @replaceRange(
+ text,
+ textArea.selectionStart,
+ textArea.selectionEnd,
+ insertText)
+ try
+ document.execCommand("ms-endUndoUnit")
+
+ @moveCursor(textArea, tag, wrap)
+
+ gl.text.moveCursor = (textArea, tag, wrapped) ->
+ return unless textArea.setSelectionRange
+
+ if textArea.selectionStart is textArea.selectionEnd
+ if wrapped
+ pos = textArea.selectionStart - tag.length
+ else
+ pos = textArea.selectionStart
+
+ textArea.setSelectionRange pos, pos
+
+ gl.text.updateText = (textArea, tag, blockTag, wrap) ->
+ $textArea = $(textArea)
+ oldVal = $textArea.val()
+ textArea = $textArea.get(0)
+ text = $textArea.val()
+ selected = @selectedText(text, textArea)
+ $textArea.focus()
+
+ @insertText(textArea, text, tag, blockTag, selected, wrap)
+
+ gl.text.init = (form) ->
+ self = @
+ $('.js-md', form)
+ .off 'click'
+ .on 'click', ->
+ $this = $(@)
+ self.updateText(
+ $this.closest('.md-area').find('textarea'),
+ $this.data('md-tag'),
+ $this.data('md-block'),
+ not $this.data('md-prepend')
+ )
+
+ gl.text.removeListeners = (form) ->
+ $('.js-md', form).off()
+
+) window
diff --git a/app/assets/javascripts/lib/type_utility.js.coffee b/app/assets/javascripts/lib/utils/type_utility.js.coffee
index 957f0d86b36..957f0d86b36 100644
--- a/app/assets/javascripts/lib/type_utility.js.coffee
+++ b/app/assets/javascripts/lib/utils/type_utility.js.coffee
diff --git a/app/assets/javascripts/lib/url_utility.js.coffee b/app/assets/javascripts/lib/utils/url_utility.js.coffee
index e8085e1c2e4..e8085e1c2e4 100644
--- a/app/assets/javascripts/lib/url_utility.js.coffee
+++ b/app/assets/javascripts/lib/utils/url_utility.js.coffee
diff --git a/app/assets/javascripts/lib/utf8_encode.js b/app/assets/javascripts/lib/utils/utf8_encode.js
index 39ffe44dae0..39ffe44dae0 100644
--- a/app/assets/javascripts/lib/utf8_encode.js
+++ b/app/assets/javascripts/lib/utils/utf8_encode.js
diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee
index 894f80586f1..86539e0d725 100644
--- a/app/assets/javascripts/merge_request_tabs.js.coffee
+++ b/app/assets/javascripts/merge_request_tabs.js.coffee
@@ -153,17 +153,18 @@ class @MergeRequestTabs
loadDiff: (source) ->
return if @diffsLoaded
-
@_get
url: "#{source}.json" + @_location.search
success: (data) =>
$('#diffs').html data.html
gl.utils.localTimeAgo($('.js-timeago', 'div#diffs'))
$('#diffs .js-syntax-highlight').syntaxHighlight()
+ $('#diffs .diff-file').singleFileDiff()
@expandViewContainer() if @diffViewType() is 'parallel'
@diffsLoaded = true
@scrollToElement("#diffs")
@highlighSelectedLine()
+ @filesCommentButton = $('.files .diff-file').filesCommentButton()
$(document)
.off 'click', '.diff-line-num a'
diff --git a/app/assets/javascripts/milestone.js.coffee b/app/assets/javascripts/milestone.js.coffee
index 0037a3a21c2..a19e68b39e2 100644
--- a/app/assets/javascripts/milestone.js.coffee
+++ b/app/assets/javascripts/milestone.js.coffee
@@ -4,18 +4,10 @@ class @Milestone
type: "PUT"
url: issue_url
data: data
- success: (data) ->
- if data.saved == true
- if data.assignee_avatar_url
- img_tag = $('<img/>')
- img_tag.attr('src', data.assignee_avatar_url)
- img_tag.addClass('avatar s16')
- $(li).find('.assignee-icon').html(img_tag)
- else
- $(li).find('.assignee-icon').html('')
- $(li).effect 'highlight'
- else
- new Flash("Issue update failed", 'alert')
+ success: (_data) =>
+ @successCallback(_data, li)
+ error: (data) ->
+ new Flash("Issue update failed", 'alert')
dataType: "json"
@sortIssues: (data) ->
@@ -25,9 +17,10 @@ class @Milestone
type: "PUT"
url: sort_issues_url
data: data
- success: (data) ->
- if data.saved != true
- new Flash("Issues update failed", 'alert')
+ success: (_data) =>
+ @successCallback(_data)
+ error: ->
+ new Flash("Issues update failed", 'alert')
dataType: "json"
@sortMergeRequests: (data) ->
@@ -37,9 +30,10 @@ class @Milestone
type: "PUT"
url: sort_mr_url
data: data
- success: (data) ->
- if data.saved != true
- new Flash("MR update failed", 'alert')
+ success: (_data) =>
+ @successCallback(_data)
+ error: (data) ->
+ new Flash("Issue update failed", 'alert')
dataType: "json"
@updateMergeRequest: (li, merge_request_url, data) ->
@@ -47,20 +41,23 @@ class @Milestone
type: "PUT"
url: merge_request_url
data: data
- success: (data) ->
- if data.saved == true
- if data.assignee_avatar_url
- img_tag = $('<img/>')
- img_tag.attr('src', data.assignee_avatar_url)
- img_tag.addClass('avatar s16')
- $(li).find('.assignee-icon').html(img_tag)
- else
- $(li).find('.assignee-icon').html('')
- $(li).effect 'highlight'
- else
- new Flash("Issue update failed", 'alert')
+ success: (_data) =>
+ @successCallback(_data, li)
+ error: (data) ->
+ new Flash("Issue update failed", 'alert')
dataType: "json"
+ @successCallback: (data, element) =>
+ if data.assignee
+ img_tag = $('<img/>')
+ img_tag.attr('src', data.assignee.avatar_url)
+ img_tag.addClass('avatar s16')
+ $(element).find('.assignee-icon').html(img_tag)
+ else
+ $(element).find('.assignee-icon').html('')
+
+ $(element).effect 'highlight'
+
constructor: ->
oldMouseStart = $.ui.sortable.prototype._mouseStart
$.ui.sortable.prototype._mouseStart = (event, overrideHandle, noActivation) ->
@@ -81,8 +78,10 @@ class @Milestone
stop: (event, ui) ->
$(".issues-sortable-list").css "min-height", "0px"
update: (event, ui) ->
- data = $(this).sortable("serialize")
- Milestone.sortIssues(data)
+ # Prevents sorting from container which element has been removed.
+ if $(this).find(ui.item).length > 0
+ data = $(this).sortable("serialize")
+ Milestone.sortIssues(data)
receive: (event, ui) ->
new_state = $(this).data('state')
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/network/application.js.coffee b/app/assets/javascripts/network/application.js.coffee
index cb9eead855b..f75f63869c5 100644
--- a/app/assets/javascripts/network/application.js.coffee
+++ b/app/assets/javascripts/network/application.js.coffee
@@ -4,9 +4,6 @@
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
# the compiled file.
#
-#= require raphael
-#= require g.raphael
-#= require g.bar
#= require_tree .
$ ->
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index e2d3241437b..0ea54faae1a 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -100,13 +100,43 @@ 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 #aka the up key
- 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)
initRefresh: ->
clearInterval(Notes.interval)
@@ -164,8 +194,7 @@ class @Notes
renderNote: (note) ->
unless note.valid
if note.award
- flash = new Flash('You have already awarded this emoji!', 'alert')
- flash.pinTo('.header-content')
+ new Flash('You have already awarded this emoji!', 'alert')
return
if note.award
@@ -210,12 +239,16 @@ class @Notes
@note_ids.push(note.id)
form = $("#new-discussion-note-form-#{note.discussion_id}")
+ if note.original_discussion_id? and form.length is 0
+ form = $("#new-discussion-note-form-#{note.original_discussion_id}")
row = form.closest("tr")
note_html = $(note.html)
note_html.syntaxHighlight()
# is this the first note of discussion?
discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']")
+ if note.original_discussion_id? and discussionContainer.length is 0
+ discussionContainer = $(".notes[data-discussion-id='" + note.original_discussion_id + "']")
if discussionContainer.length is 0
# insert the note and the reply button after the temp row
row.after note.discussion_html
@@ -288,8 +321,11 @@ class @Notes
form.addClass "js-main-target-form"
form.find("#note_line_code").remove()
+ form.find("#note_position").remove()
form.find("#note_type").remove()
+ @parentTimeline = form.parents('.timeline')
+
###
General note form setup.
@@ -305,10 +341,12 @@ class @Notes
new Autosave textarea, [
"Note"
- form.find("#note_commit_id").val()
- form.find("#note_line_code").val()
form.find("#note_noteable_type").val()
form.find("#note_noteable_id").val()
+ form.find("#note_commit_id").val()
+ form.find("#note_type").val()
+ form.find("#note_line_code").val()
+ form.find("#note_position").val()
]
###
@@ -320,8 +358,7 @@ class @Notes
@renderNote(note)
addNoteError: (xhr, note, status) =>
- flash = new Flash('Your comment could not be submitted! Please check your network connection and try again.', 'alert')
- flash.pinTo('.md-area')
+ new Flash('Your comment could not be submitted! Please check your network connection and try again.', 'alert', @parentTimeline)
###
Called in response to the new note form being submitted
@@ -398,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")
@@ -479,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/notifications_dropdown.js.coffee b/app/assets/javascripts/notifications_dropdown.js.coffee
index 74d2298c1fa..0bbd082c156 100644
--- a/app/assets/javascripts/notifications_dropdown.js.coffee
+++ b/app/assets/javascripts/notifications_dropdown.js.coffee
@@ -1,5 +1,5 @@
class @NotificationsDropdown
- $ ->
+ constructor: ->
$(document)
.off 'click', '.update-notification'
.on 'click', '.update-notification', (e) ->
@@ -18,7 +18,8 @@ class @NotificationsDropdown
.off 'ajax:success', '.notification-form'
.on 'ajax:success', '.notification-form', (e, data) ->
if data.saved
- new Flash('Notification settings saved', 'notice')
- $(e.currentTarget).closest('.notification-dropdown').replaceWith(data.html)
+ $(e.currentTarget)
+ .closest('.notification-dropdown')
+ .replaceWith(data.html)
else
new Flash('Failed to save new settings', 'alert')
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/project.js.coffee b/app/assets/javascripts/project.js.coffee
index d12bad97a05..3288c801388 100644
--- a/app/assets/javascripts/project.js.coffee
+++ b/app/assets/javascripts/project.js.coffee
@@ -19,6 +19,7 @@ class @Project
$('.clone').text(url)
# Ref switcher
+ @initRefSwitcher()
$('.project-refs-select').on 'change', ->
$(@).parents('form').submit()
@@ -34,7 +35,6 @@ class @Project
$(@).parents('.no-password-message').remove()
e.preventDefault()
-
@projectSelectDropdown()
projectSelectDropdown: ->
@@ -50,3 +50,42 @@ class @Project
changeProject: (url) ->
window.location = url
+
+ initRefSwitcher: ->
+ $('.js-project-refs-dropdown').each ->
+ $dropdown = $(@)
+ selected = $dropdown.data('selected')
+
+ $dropdown.glDropdown(
+ data: (term, callback) ->
+ $.ajax(
+ url: $dropdown.data('refs-url')
+ data:
+ ref: $dropdown.data('ref')
+ ).done (refs) ->
+ callback(refs)
+ selectable: true
+ filterable: true
+ filterByText: true
+ fieldName: 'ref'
+ renderRow: (ref) ->
+ if ref.header?
+ $('<li />')
+ .addClass('dropdown-header')
+ .text(ref.header)
+ else
+ link = $('<a />')
+ .attr('href', '#')
+ .addClass(if ref is selected then 'is-active' else '')
+ .text(ref)
+ .attr('data-ref', escape(ref))
+
+ $('<li />')
+ .append(link)
+ id: (obj, $el) ->
+ $el.attr('data-ref')
+ toggleLabel: (obj, $el) ->
+ $el.text().trim()
+ clicked: (e) ->
+ $dropdown.closest('form').submit()
+ )
diff --git a/app/assets/javascripts/projects_list.js.coffee b/app/assets/javascripts/projects_list.js.coffee
index e4c4bf3b273..a7d78d9e461 100644
--- a/app/assets/javascripts/projects_list.js.coffee
+++ b/app/assets/javascripts/projects_list.js.coffee
@@ -5,13 +5,12 @@
this.initPagination()
initSearch: ->
- @timer = null
- $(".projects-list-filter").on('keyup', ->
- clearTimeout(@timer)
- @timer = setTimeout(ProjectsList.filterResults, 500)
- )
+ projectsListFilter = $('.projects-list-filter')
+ debounceFilter = _.debounce ProjectsList.filterResults, 500
+ projectsListFilter.on 'keyup', (e) ->
+ debounceFilter() if projectsListFilter.val() isnt ''
- filterResults: =>
+ filterResults: ->
$('.projects-list-holder').fadeTo(250, 0.5)
form = null
diff --git a/app/assets/javascripts/protected_branch_select.js.coffee b/app/assets/javascripts/protected_branch_select.js.coffee
new file mode 100644
index 00000000000..6d45770ace9
--- /dev/null
+++ b/app/assets/javascripts/protected_branch_select.js.coffee
@@ -0,0 +1,40 @@
+class @ProtectedBranchSelect
+ constructor: (currentProject) ->
+ $('.dropdown-footer').hide();
+ @dropdown = $('.js-protected-branch-select').glDropdown(
+ data: @getProtectedBranches
+ filterable: true
+ remote: false
+ search:
+ fields: ['title']
+ selectable: true
+ toggleLabel: (selected) -> if (selected and 'id' of selected) then selected.title else 'Protected Branch'
+ fieldName: 'protected_branch[name]'
+ text: (protected_branch) -> _.escape(protected_branch.title)
+ id: (protected_branch) -> _.escape(protected_branch.id)
+ onFilter: @toggleCreateNewButton
+ clicked: () -> $('.protect-branch-btn').attr('disabled', false)
+ )
+
+ $('.create-new-protected-branch').on 'click', (event) =>
+ # Refresh the dropdown's data, which ends up calling `getProtectedBranches`
+ @dropdown.data('glDropdown').remote.execute()
+ @dropdown.data('glDropdown').selectRowAtIndex(event, 0)
+
+ getProtectedBranches: (term, callback) =>
+ if @selectedBranch
+ callback(gon.open_branches.concat(@selectedBranch))
+ else
+ callback(gon.open_branches)
+
+ toggleCreateNewButton: (branchName) =>
+ @selectedBranch = { title: branchName, id: branchName, text: branchName }
+
+ if branchName is ''
+ $('.protected-branch-select-footer-list').addClass('hidden')
+ $('.dropdown-footer').hide();
+ else
+ $('.create-new-protected-branch').text("Create Protected Branch: #{branchName}")
+ $('.protected-branch-select-footer-list').removeClass('hidden')
+ $('.dropdown-footer').show();
+
diff --git a/app/assets/javascripts/protected_branches.js.coffee b/app/assets/javascripts/protected_branches.js.coffee
index 5753c9d4e72..14afef2e2ee 100644
--- a/app/assets/javascripts/protected_branches.js.coffee
+++ b/app/assets/javascripts/protected_branches.js.coffee
@@ -1,17 +1,18 @@
$ ->
$(".protected-branches-list :checkbox").change (e) ->
name = $(this).attr("name")
- if name == "developers_can_push"
+ if name == "developers_can_push" || name == "developers_can_merge"
id = $(this).val()
- checked = $(this).is(":checked")
+ can_push = $(this).is(":checked")
url = $(this).data("url")
$.ajax
- type: "PUT"
+ type: "PATCH"
url: url
dataType: "json"
data:
id: id
- developers_can_push: checked
+ protected_branch:
+ "#{name}": can_push
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..8c8689bacee 100644
--- a/app/assets/javascripts/shortcuts.js.coffee
+++ b/app/assets/javascripts/shortcuts.js.coffee
@@ -2,19 +2,20 @@ class @Shortcuts
constructor: (skipResetBindings) ->
@enabledHelp = []
Mousetrap.reset() if not skipResetBindings
- Mousetrap.bind('?', @onToggleHelp)
- Mousetrap.bind('s', Shortcuts.focusSearch)
- Mousetrap.bind(['ctrl+shift+p', 'command+shift+p'], @toggleMarkdownPreview)
+ Mousetrap.bind '?', @onToggleHelp
+ Mousetrap.bind 's', Shortcuts.focusSearch
+ Mousetrap.bind 'f', (e) => @focusFilter e
+ Mousetrap.bind ['ctrl+shift+p', 'command+shift+p'], @toggleMarkdownPreview
Mousetrap.bind('t', -> Turbolinks.visit(findFileURL)) if findFileURL?
onToggleHelp: (e) =>
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
@@ -32,10 +33,16 @@ class @Shortcuts
$('.js-more-help-button').remove()
)
+ focusFilter: (e) ->
+ @filterInput ?= $('input[type=search]', '.nav-controls')
+ @filterInput.focus()
+ e.preventDefault()
+
@focusSearch: (e) ->
$('#search').focus()
e.preventDefault()
+
$(document).on 'click.more_help', '.js-more-help-button', (e) ->
$(@).remove()
$('.hidden-shortcut').show()
diff --git a/app/assets/javascripts/single_file_diff.js.coffee b/app/assets/javascripts/single_file_diff.js.coffee
new file mode 100644
index 00000000000..f3e225c3728
--- /dev/null
+++ b/app/assets/javascripts/single_file_diff.js.coffee
@@ -0,0 +1,54 @@
+class @SingleFileDiff
+
+ WRAPPER = '<div class="diff-content diff-wrap-lines"></div>'
+ LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>'
+ ERROR_HTML = '<div class="nothing-here-block"><i class="fa fa-warning"></i> Could not load diff</div>'
+ COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. Click to expand it.</div>'
+
+ constructor: (@file) ->
+ @content = $('.diff-content', @file)
+ @diffForPath = @content.find('[data-diff-for-path]').data 'diff-for-path'
+ @isOpen = !@diffForPath
+
+ if @diffForPath
+ @collapsedContent = @content
+ @loadingContent = $(WRAPPER).addClass('loading').html(LOADING_HTML).hide()
+ @content = null
+ @collapsedContent.after(@loadingContent)
+ else
+ @collapsedContent = $(WRAPPER).html(COLLAPSED_HTML).hide()
+ @content.after(@collapsedContent)
+
+ @collapsedContent.on 'click', @toggleDiff
+
+ $('.file-title > a', @file).on 'click', @toggleDiff
+
+ toggleDiff: (e) =>
+ @isOpen = !@isOpen
+ if not @isOpen and not @hasError
+ @content.hide()
+ @collapsedContent.show()
+ else if @content
+ @collapsedContent.hide()
+ @content.show()
+ else
+ @getContentHTML()
+
+ getContentHTML: ->
+ @collapsedContent.hide()
+ @loadingContent.show()
+ $.get @diffForPath, (data) =>
+ @loadingContent.hide()
+ if data.html
+ @content = $(data.html)
+ @content.syntaxHighlight()
+ else
+ @hasError = true
+ @content = $(ERROR_HTML)
+ @collapsedContent.after(@content)
+ return
+
+$.fn.singleFileDiff = ->
+ return @each ->
+ if not $.data this, 'singleFileDiff'
+ $.data this, 'singleFileDiff', new SingleFileDiff this
diff --git a/app/assets/javascripts/subscription_select.js.coffee b/app/assets/javascripts/subscription_select.js.coffee
new file mode 100644
index 00000000000..e5eb7a50d80
--- /dev/null
+++ b/app/assets/javascripts/subscription_select.js.coffee
@@ -0,0 +1,18 @@
+class @SubscriptionSelect
+ constructor: ->
+ $('.js-subscription-event').each (i, el) ->
+ fieldName = $(el).data("field-name")
+
+ $(el).glDropdown(
+ selectable: true
+ fieldName: fieldName
+ toggleLabel: (selected, el, instance) =>
+ label = 'Subscription'
+ $item = instance.dropdown.find('.is-active')
+ label = $item.text() if $item.length
+ label
+ clicked: (item, $el, e)->
+ e.preventDefault()
+ id: (obj, el) ->
+ $(el).data("id")
+ )
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/u2f/authenticate.js.coffee b/app/assets/javascripts/u2f/authenticate.js.coffee
index 6deb902c8de..918c0a560fd 100644
--- a/app/assets/javascripts/u2f/authenticate.js.coffee
+++ b/app/assets/javascripts/u2f/authenticate.js.coffee
@@ -6,8 +6,20 @@
class @U2FAuthenticate
constructor: (@container, u2fParams) ->
@appId = u2fParams.app_id
- @challenges = u2fParams.challenges
- @signRequests = u2fParams.sign_requests
+ @challenge = u2fParams.challenge
+
+ # The U2F Javascript API v1.1 requires a single challenge, with
+ # _no challenges per-request_. The U2F Javascript API v1.0 requires a
+ # challenge per-request, which is done by copying the single challenge
+ # into every request.
+ #
+ # In either case, we don't need the per-request challenges that the server
+ # has generated, so we can remove them.
+ #
+ # Note: The server library fixes this behaviour in (unreleased) version 1.0.0.
+ # This can be removed once we upgrade.
+ # https://github.com/castle/ruby-u2f/commit/103f428071a81cd3d5f80c2e77d522d5029946a4
+ @signRequests = u2fParams.sign_requests.map (request) -> _(request).omit('challenge')
start: () =>
if U2FUtil.isU2FSupported()
@@ -16,7 +28,7 @@ class @U2FAuthenticate
@renderNotSupported()
authenticate: () =>
- u2f.sign(@appId, @challenges, @signRequests, (response) =>
+ u2f.sign(@appId, @challenge, @signRequests, (response) =>
if response.errorCode
error = new U2FError(response.errorCode)
@renderError(error);
diff --git a/app/assets/javascripts/u2f/util.js.coffee b/app/assets/javascripts/u2f/util.js.coffee
new file mode 100644
index 00000000000..5ef324f609d
--- /dev/null
+++ b/app/assets/javascripts/u2f/util.js.coffee
@@ -0,0 +1,3 @@
+class @U2FUtil
+ @isU2FSupported: ->
+ window.u2f
diff --git a/app/assets/javascripts/u2f/util.js.coffee.erb b/app/assets/javascripts/u2f/util.js.coffee.erb
deleted file mode 100644
index d59341c38b9..00000000000
--- a/app/assets/javascripts/u2f/util.js.coffee.erb
+++ /dev/null
@@ -1,15 +0,0 @@
-# Helper class for U2F (universal 2nd factor) device registration and authentication.
-
-class @U2FUtil
- @isU2FSupported: ->
- if @testMode
- true
- else
- gon.u2f.browser_supports_u2f
-
- @enableTestMode: ->
- @testMode = true
-
-<% if Rails.env.test? %>
-U2FUtil.enableTestMode();
-<% end %>
diff --git a/app/assets/javascripts/users/application.js.coffee b/app/assets/javascripts/users/application.js.coffee
index 647ffbf5f45..91cacfece46 100644
--- a/app/assets/javascripts/users/application.js.coffee
+++ b/app/assets/javascripts/users/application.js.coffee
@@ -1,8 +1,2 @@
-# This is a manifest file that'll be compiled into including all the files listed below.
-# Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
-# be included in the compiled file accessible from http://example.com/assets/application.js
-# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
-# the compiled file.
#
-#= require d3
#= require_tree .
diff --git a/app/assets/javascripts/users/calendar.js.coffee b/app/assets/javascripts/users/calendar.js.coffee
index c081f023b04..c49ba5186f2 100644
--- a/app/assets/javascripts/users/calendar.js.coffee
+++ b/app/assets/javascripts/users/calendar.js.coffee
@@ -87,14 +87,15 @@ class @Calendar
.attr 'width', @daySize
.attr 'height', @daySize
.attr 'title', (stamp) =>
+ date = new Date(stamp.date)
contribText = 'No contributions'
if stamp.count > 0
contribText = "#{stamp.count} contribution#{if stamp.count > 1 then 's' else ''}"
- date = dateFormat(stamp.date, 'mmm d, yyyy')
+ dateText = dateFormat(date, 'mmm d, yyyy')
- "#{contribText}<br />#{date}"
+ "#{contribText}<br />#{gl.utils.getDayName(date)} #{dateText}"
.attr 'class', 'user-contrib-cell js-tooltip'
.attr 'fill', (stamp) =>
if stamp.count isnt 0
diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee
index 2548efb2186..344be811e0d 100644
--- a/app/assets/javascripts/users_select.js.coffee
+++ b/app/assets/javascripts/users_select.js.coffee
@@ -56,14 +56,18 @@ class @UsersSelect
username: ''
avatar: ''
$value.html(assigneeTemplate(user))
+
+ $collapsedSidebar
+ .attr('title', user.name)
+ .tooltip('fixTitle')
+
$collapsedSidebar.html(collapsedAssigneeTemplate(user))
collapsedAssigneeTemplate = _.template(
'<% if( avatar ) { %>
- <a class="author_link" href="/u/<%= username %>">
- <img width="24" class="avatar avatar-inline s24" alt="" src="<%= avatar %>">
- <span class="author">Toni Boehm</span>
+ <a class="author_link" href="/u/<%- username %>">
+ <img width="24" class="avatar avatar-inline s24" alt="" src="<%- avatar %>">
</a>
<% } else { %>
<i class="fa fa-user"></i>
@@ -72,13 +76,13 @@ class @UsersSelect
assigneeTemplate = _.template(
'<% if (username) { %>
- <a class="author_link bold" href="/u/<%= username %>">
+ <a class="author_link bold" href="/u/<%- username %>">
<% if( avatar ) { %>
- <img width="32" class="avatar avatar-inline s32" alt="" src="<%= avatar %>">
+ <img width="32" class="avatar avatar-inline s32" alt="" src="<%- avatar %>">
<% } %>
- <span class="author"><%= name %></span>
+ <span class="author"><%- name %></span>
<span class="username">
- @<%= username %>
+ @<%- username %>
</span>
</a>
<% } else { %>
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index 3cbddc59f11..a306b8f3f29 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -37,3 +37,4 @@
@import "framework/timeline.scss";
@import "framework/typography.scss";
@import "framework/zen.scss";
+@import "framework/blank";
diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss
index bb8d71fbae8..8b6ddf8ba18 100644
--- a/app/assets/stylesheets/framework/avatar.scss
+++ b/app/assets/stylesheets/framework/avatar.scss
@@ -20,6 +20,7 @@
}
&.s16 { width: 16px; height: 16px; margin-right: 6px; }
+ &.s20 { width: 20px; height: 20px; margin-right: 7px; }
&.s24 { width: 24px; height: 24px; margin-right: 8px; }
&.s26 { width: 26px; height: 26px; margin-right: 8px; }
&.s32 { width: 32px; height: 32px; margin-right: 10px; }
diff --git a/app/assets/stylesheets/framework/blank.scss b/app/assets/stylesheets/framework/blank.scss
new file mode 100644
index 00000000000..540718197e0
--- /dev/null
+++ b/app/assets/stylesheets/framework/blank.scss
@@ -0,0 +1,51 @@
+.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;
+ text-align: center;
+}
+
+.blank-state-no-icon {
+ padding-top: 40px;
+ padding-bottom: 40px;
+}
+
+.blank-state-icon {
+ padding-bottom: 20px;
+ color: $gray-darkest;
+ font-size: 56px;
+
+ path,
+ polygon {
+ fill: currentColor;
+ }
+}
+
+.blank-state-title {
+ margin-top: 0;
+ margin-bottom: 5px;
+ font-size: 19px;
+ font-weight: normal;
+}
+
+.blank-state-text {
+ margin-top: 0;
+ margin-bottom: $gl-padding;
+ font-size: 15px;
+
+ > strong {
+ font-weight: 600;
+ }
+}
+
+.blank-state-welcome-title {
+ font-size: 24px;
+}
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index d5fe5bc2ef1..ad94e457cfd 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -16,6 +16,15 @@
font-weight: normal;
font-size: 16px;
line-height: 36px;
+
+ &.diff-collapsed {
+ padding: 5px;
+ cursor: pointer;
+
+ &:hover {
+ background-color: $row-hover;
+ }
+ }
}
.row-content-block {
@@ -97,6 +106,22 @@
}
}
+.sub-header-block {
+ background-color: $white-light;
+ border-bottom: 1px solid $white-dark;
+ padding: 11px 0;
+ margin-bottom: 11px;
+
+ .oneline {
+ line-height: 35px;
+ }
+
+ &.no-bottom-space {
+ border-bottom: 0;
+ margin-bottom: 0;
+ }
+}
+
.cover-block {
text-align: center;
background: $background-color;
@@ -121,7 +146,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..f87b8a2ad1c 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -49,6 +49,17 @@
border-color: $border-dark;
color: $color;
}
+
+ svg {
+
+ path {
+ fill: $color;
+ }
+
+ use {
+ stroke: $color;
+ }
+ }
}
@mixin btn-green {
@@ -173,6 +184,13 @@
.caret {
margin-left: 5px;
}
+
+ svg {
+ height: 15px;
+ width: auto;
+ position: relative;
+ top: 2px;
+ }
}
.btn-lg {
@@ -281,3 +299,21 @@
color: $gl-icon-color;
}
}
+
+.clone-dropdown-btn a {
+ color: $dropdown-link-color;
+ &:hover {
+ text-decoration: none;
+ }
+}
+
+.btn-static {
+ background-color: $background-color !important;
+ border: 1px solid lightgrey;
+ cursor: default;
+ &:active {
+ -moz-box-shadow: inset 0 0 0 white;
+ -webkit-box-shadow: inset 0 0 0 white;
+ box-shadow: inset 0 0 0 white;
+ }
+}
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index f8aecd0558d..c1e5305644b 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -270,21 +270,6 @@ table {
}
}
-.dashboard-intro-icon {
- float: left;
- text-align: center;
- font-size: 32px;
- color: #aaa;
- width: 60px;
-}
-
-.dashboard-intro-text {
- display: inline-block;
- margin-left: -60px;
- padding-left: 60px;
- width: 100%;
-}
-
.btn-sign-in {
text-shadow: none;
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index d4d579a083d..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;
@@ -461,10 +470,12 @@
}
}
- .ui-state-active,
- .ui-state-hover {
- color: $md-link-color;
- background-color: $calendar-hover-bg;
+ .ui-datepicker-calendar {
+ .ui-state-hover,
+ .ui-state-active {
+ color: #fff;
+ border: 0;
+ }
}
.ui-datepicker-prev,
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 71a9f79be3e..407f1873431 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 {
@@ -70,7 +70,7 @@
}
&.wiki {
- padding: $gl-padding;
+ padding: 30px $gl-padding;
.highlight {
margin-bottom: 9px;
diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss
index 1bfd0213995..0c21d0240b3 100644
--- a/app/assets/stylesheets/framework/flash.scss
+++ b/app/assets/stylesheets/framework/flash.scss
@@ -1,8 +1,8 @@
.flash-container {
cursor: pointer;
margin: 0;
+ margin-bottom: $gl-padding;
font-size: 14px;
- width: 100%;
z-index: 100;
.flash-notice {
@@ -16,4 +16,29 @@
@extend .alert-danger;
margin: 0;
}
+
+ .flash-notice, .flash-alert {
+ border-radius: $border-radius-default;
+
+ .container-fluid.container-limited.flash-text {
+ background: transparent;
+ }
+ }
+
+ &.flash-container-page {
+ margin-bottom: 0;
+
+ .flash-notice, .flash-alert {
+ border-radius: 0;
+ }
+ }
}
+
+@media (max-width: $screen-md-min) {
+ ul.notes {
+ .flash-container.timeline-content {
+ margin-left: 0;
+ }
+ }
+}
+
diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss
index 0a8603b6702..3673b81f183 100644
--- a/app/assets/stylesheets/framework/gitlab-theme.scss
+++ b/app/assets/stylesheets/framework/gitlab-theme.scss
@@ -11,7 +11,6 @@
.toggle-nav-collapse,
.pin-nav-btn {
color: $color-light;
- background: $color;
&:hover {
color: $white-light;
@@ -20,17 +19,6 @@
.sidebar-wrapper {
background: $color-darker;
-
- .sidebar-user {
- background: $color-darker;
- color: $color-light;
-
- &:hover {
- background-color: $color-dark;
- color: $white-light;
- text-decoration: none;
- }
- }
}
.nav-sidebar li {
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index a7bcb456560..0c607071840 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -26,7 +26,6 @@ header {
text-align: center;
#tanuki-logo, img {
- width: 36px;
height: 36px;
}
}
@@ -61,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;
@@ -132,6 +131,10 @@ header {
transition-duration: .3s;
z-index: 999;
+ svg, img {
+ height: 36px;
+ }
+
&:hover {
cursor: pointer;
}
@@ -238,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..2c40ec430ca 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -137,6 +137,15 @@ ul.content-list {
padding-top: 1px;
float: right;
+ > .control-text {
+ margin-right: $gl-padding-top;
+ line-height: 40px;
+
+ &:last-child {
+ margin-right: 0;
+ }
+ }
+
> .btn,
> .btn-group {
margin-right: $gl-padding-top;
@@ -166,6 +175,12 @@ ul.content-list {
.panel > .content-list > li {
padding: $gl-padding-top $gl-padding;
+
+ &.commit {
+ @media (min-width: $screen-sm-min) {
+ padding-left: 46px + $gl-padding;
+ }
+ }
}
ul.controls {
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index fd885b38680..5d3273ea64d 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -65,6 +65,11 @@
a {
padding-top: 0;
line-height: 1;
+ border-bottom: 1px solid $border-color;
+
+ &.btn.btn-xs {
+ padding: 2px 5px;
+ }
}
}
}
@@ -97,5 +102,31 @@
white-space: pre-wrap;
word-break: keep-all;
}
+
+ @include bulleted-list;
+ }
+}
+
+.toolbar-group {
+ float: left;
+ margin-right: -5px;
+ margin-left: $gl-padding;
+
+ &:first-child {
+ margin-left: 0;
+ }
+}
+
+.toolbar-btn {
+ float: left;
+ padding: 0 5px;
+ color: #959494;
+ background: transparent;
+ border: 0;
+ outline: 0;
+
+ &:hover,
+ &:focus {
+ color: $gl-link-color;
}
}
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index 828e7224231..5ec5a96a597 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -110,3 +110,17 @@
font-size: 16px;
line-height: 24px;
}
+
+@mixin bulleted-list {
+ > ul {
+ list-style-type: disc;
+
+ ul {
+ list-style-type: circle;
+
+ ul {
+ list-style-type: square;
+ }
+ }
+ }
+} \ No newline at end of file
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 a55918f8711..364952d3b4a 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -1,6 +1,6 @@
@mixin fade($gradient-direction, $rgba, $gradient-color) {
- visibility: visible;
- opacity: 1;
+ visibility: hidden;
+ opacity: 0;
z-index: 2;
position: absolute;
bottom: 12px;
@@ -13,11 +13,17 @@
background: -moz-linear-gradient($gradient-direction, $rgba, $gradient-color 45%);
background: linear-gradient($gradient-direction, $rgba, $gradient-color 45%);
- &.end-scroll {
- visibility: hidden;
- opacity: 0;
+ &.scrolling {
+ visibility: visible;
+ opacity: 1;
transition-duration: .3s;
}
+
+ .fa {
+ position: relative;
+ top: 5px;
+ font-size: 18px;
+ }
}
@mixin scrolling-links() {
@@ -25,6 +31,7 @@
overflow-x: auto;
overflow-y: hidden;
-webkit-overflow-scrolling: touch;
+
&::-webkit-scrollbar {
display: none;
}
@@ -70,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;
}
@@ -104,10 +111,6 @@
width: 50%;
line-height: 28px;
- &.wiki-page {
- padding: 16px 10px 11px;
- }
-
/* Small devices (phones, tablets, 768px and lower) */
@media (max-width: $screen-sm-min) {
width: 100%;
@@ -131,12 +134,17 @@
margin-bottom: 0;
border-bottom: none;
+ &.wide {
+ width: 100%;
+ display: block;
+ }
+
li a {
padding: 16px 10px 11px;
}
/* Small devices (phones, tablets, 768px and lower) */
- @media (max-width: $screen-sm-max) {
+ @media (max-width: $screen-xs-max) {
width: 100%;
}
}
@@ -161,6 +169,7 @@
> .btn {
margin-right: $gl-padding-top;
display: inline-block;
+ vertical-align: top;
&:last-child {
margin-right: 0;
@@ -220,6 +229,7 @@
form {
display: block;
height: auto;
+ margin-bottom: 14px;
input {
width: 100%;
@@ -268,7 +278,7 @@
float: right;
padding: 7px 0 0;
- @media (max-width: $screen-xs-max) {
+ @media (max-width: $screen-sm-max) {
display: none;
}
@@ -299,33 +309,9 @@
}
.nav-links {
- @include scrolling-links();
border-bottom: none;
height: 51px;
- svg {
- position: relative;
- top: 2px;
- margin-right: 2px;
- height: 15px;
- width: auto;
-
- path,
- polygon {
- fill: $layout-link-gray;
- }
- }
-
- .fade-right {
- @include fade(left, rgba(250, 250, 250, 0.4), $background-color);
- right: 0;
- }
-
- .fade-left {
- @include fade(right, rgba(250, 250, 250, 0.4), $background-color);
- left: 0;
- }
-
li {
a {
@@ -361,18 +347,6 @@
}
}
}
-
- .nav-control {
-
- .fade-right {
- @media (min-width: $screen-xs-max) {
- right: 68px;
- }
- @media (max-width: $screen-xs-min) {
- right: 0;
- }
- }
- }
}
.scrolling-tabs-container {
@@ -380,15 +354,42 @@
.nav-links {
@include scrolling-links();
+ }
+
+ .fade-right {
+ @include fade(left, rgba(255, 255, 255, 0.4), $background-color);
+ right: -5px;
+
+ .fa {
+ right: -7px;
+ }
+ }
+
+ .fade-left {
+ @include fade(right, rgba(255, 255, 255, 0.4), $background-color);
+ left: -5px;
+
+ .fa {
+ left: -7px;
+ }
+ }
+
+ &.sub-nav-scroll {
.fade-right {
- @include fade(left, rgba(255, 255, 255, 0.4), $background-color);
right: 0;
+
+ .fa {
+ right: -23px;
+ }
}
.fade-left {
- @include fade(right, rgba(255, 255, 255, 0.4), $background-color);
left: 0;
+
+ .fa {
+ left: 10px;
+ }
}
}
}
@@ -401,21 +402,19 @@
.fade-right {
@include fade(left, rgba(255, 255, 255, 0.4), $white-light);
- right: 0;
+ right: -5px;
+
+ .fa {
+ right: -7px;
+ }
}
.fade-left {
@include fade(right, rgba(255, 255, 255, 0.4), $white-light);
- left: 0;
- }
-
- &.event-filter {
- .fade-right {
- visibility: hidden;
+ left: -5px;
- @media (max-width: $screen-xs-max) {
- visibility: visible;
- }
+ .fa {
+ left: -7px;
}
}
}
diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss
index ae7bdf14c40..874416e1007 100644
--- a/app/assets/stylesheets/framework/panels.scss
+++ b/app/assets/stylesheets/framework/panels.scss
@@ -9,6 +9,10 @@
margin-top: -2px;
float: right;
}
+
+ .dropdown-menu-toggle {
+ line-height: 20px;
+ }
}
.panel-body {
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index f242706ebe4..21d87cc9d34 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -165,11 +165,6 @@
background-size: 16px 16px !important;
}
-/** Branch/tag selector **/
-.project-refs-form .select2-container {
- width: 160px !important;
-}
-
.select2-results .select2-no-results,
.select2-results .select2-searching,
.select2-results .select2-ajax-error,
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index a0bb3427af0..3fa4a22258d 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -1,7 +1,14 @@
.page-with-sidebar {
padding-top: $header-height;
+ padding-bottom: 25px;
transition: padding $sidebar-transition-duration;
+ &.page-sidebar-pinned {
+ .sidebar-wrapper {
+ @include box-shadow(none);
+ }
+ }
+
.sidebar-wrapper {
position: fixed;
top: 0;
@@ -10,6 +17,7 @@
height: 100%;
overflow: hidden;
transition: width $sidebar-transition-duration;
+ @include box-shadow(2px 0 16px 0 $black-transparent);
}
}
@@ -39,34 +47,14 @@
}
}
-.sidebar-user {
- padding: 15px;
- position: absolute;
- left: 0;
- bottom: 0;
- width: $sidebar_width;
- overflow: hidden;
- font-size: 16px;
- line-height: 36px;
- transition: width $sidebar-transition-duration, padding $sidebar-transition-duration;
-
- @media (min-width: $sidebar-breakpoint) {
- bottom: 50px;
- }
-}
-
.nav-sidebar {
position: absolute;
top: 50px;
- bottom: 65px;
+ bottom: 0;
width: $sidebar_width;
overflow-y: auto;
overflow-x: hidden;
- @media (min-width: $sidebar-breakpoint) {
- bottom: 115px;
- }
-
&.navbar-collapse {
padding: 0 !important;
}
@@ -84,14 +72,13 @@
}
a {
- padding: 7px 15px 7px 12px;
+ padding: 7px $gl-sidebar-padding;
font-size: $gl-font-size;
line-height: 24px;
display: block;
text-decoration: none;
font-weight: normal;
outline: none;
- white-space: nowrap;
&:hover,
&:active,
@@ -117,7 +104,7 @@
}
}
-.toggle-nav-collapse {
+.sidebar-action-buttons {
width: $sidebar_width;
position: absolute;
top: 0;
@@ -126,12 +113,37 @@
padding: 5px 0;
font-size: 18px;
line-height: 30px;
+
+ .toggle-nav-collapse {
+ left: 0;
+ }
+
+ .pin-nav-btn {
+ right: 0;
+ display: none;
+
+ @media (min-width: $sidebar-breakpoint) {
+ display: block;
+ }
+
+ .fa {
+ transition: transform .15s;
+ }
+
+ &.is-active {
+ .fa {
+ transform: rotate(90deg);
+ }
+ }
+ }
}
.nav-header-btn {
- padding: 10px 5px;
+ padding: 10px $gl-sidebar-padding;
color: inherit;
transition-duration: .3s;
+ position: absolute;
+ top: 0;
&:hover,
&:focus {
@@ -140,30 +152,6 @@
}
}
-.pin-nav-btn {
- display: none;
- position: absolute;
- left: 0;
- bottom: 0;
- height: 50px;
- width: $sidebar_width;
- line-height: 30px;
-
- @media (min-width: $sidebar-breakpoint) {
- display: block;
- }
-
- .fa {
- transition: transform .15s;
- }
-
- &.is-active {
- .fa {
- transform: rotate(90deg);
- }
- }
-}
-
.page-sidebar-collapsed {
padding-left: 0;
@@ -210,6 +198,10 @@ header.header-pinned-nav {
.sidebar-collapsed-icon {
cursor: pointer;
+
+ .btn {
+ background-color: $gray-light;
+ }
}
}
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 3575984b229..8659604cb8b 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -37,39 +37,41 @@
}
h1 {
- font-size: 1.3em;
+ font-size: 2em;
font-weight: 600;
- margin: 24px 0 12px;
- padding: 0 0 10px;
- border-bottom: 1px solid #e7e9ed;
+ margin: 1em 0 10px;
+ padding: 0 0 0.3em;
+ border-bottom: 1px solid $btn-default-border;
color: $gl-gray-dark;
}
h2 {
- font-size: 1.2em;
+ font-size: 1.6em;
font-weight: 600;
- margin: 24px 0 12px;
+ margin: 1em 0 10px;
+ padding-bottom: 0.3em;
+ border-bottom: 1px solid $btn-default-border;
color: $gl-gray-dark;
}
h3 {
- margin: 24px 0 12px;
- font-size: 1.1em;
+ margin: 1em 0 10px;
+ font-size: 1.4em;
}
h4 {
- margin: 24px 0 12px;
- font-size: 0.98em;
+ margin: 1em 0 10px;
+ font-size: 1.25em;
}
h5 {
- margin: 24px 0 12px;
- font-size: 0.95em;
+ margin: 1em 0 10px;
+ font-size: 1em;
}
h6 {
- margin: 24px 0 12px;
- font-size: 0.90em;
+ margin: 1em 0 10px;
+ font-size: 0.95em;
}
blockquote {
@@ -115,7 +117,7 @@
ul, ol {
padding: 0;
- margin: 6px 0 6px 28px !important;
+ margin: 3px 0 3px 28px !important;
}
li {
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index c37574ca7a1..1882d4e888d 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -7,15 +7,17 @@ $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: #f5f5f5;
+$table-text-gray: #8f8f8f;
/*
* Text
@@ -62,6 +64,7 @@ $gl-btn-padding: 10px;
$gl-input-padding: 10px;
$gl-vert-padding: 6px;
$gl-padding-top: 10px;
+$gl-sidebar-padding: 22px;
/*
* Misc
@@ -153,9 +156,6 @@ $warning-message-bg: #fbf2d9;
$warning-message-color: #9e8e60;
$warning-message-border: #f0e2bb;
-/* header */
-$light-grey-header: #faf9f9;
-
/* tanuki logo colors */
$tanuki-red: #e24329;
$tanuki-orange: #fc6d26;
diff --git a/app/assets/stylesheets/pages/admin.scss b/app/assets/stylesheets/pages/admin.scss
index e05f14e7496..5607239d92d 100644
--- a/app/assets/stylesheets/pages/admin.scss
+++ b/app/assets/stylesheets/pages/admin.scss
@@ -71,3 +71,30 @@
@extend .broadcast-message;
margin-bottom: 20px;
}
+
+
+// Users List
+
+.users-list {
+ .user-row {
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ }
+
+ .user-details {
+ flex: 1 1 auto;
+ }
+
+ .user-name {
+ display: inline-block;
+ font-weight: 600;
+ }
+
+ .dropdown {
+ .btn-block {
+ margin-bottom: 0;
+ line-height: inherit;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/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/builds.scss b/app/assets/stylesheets/pages/builds.scss
index e8f1935d239..99a2cd306cf 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -83,14 +83,6 @@
}
}
-table.builds {
- .build-link {
- a {
- color: $gl-dark-link-color;
- }
- }
-}
-
.build-trace {
background: $ci-output-bg;
color: $ci-text-color;
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index 761e33f0df7..0298577c494 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -61,7 +61,7 @@
font-size: 0;
}
- .btn-transparent {
+ .btn-clipboard, .btn-transparent {
padding-left: 0;
padding-right: 0;
}
@@ -80,6 +80,7 @@
.commit {
padding: 10px 0;
+ position: relative;
@media (min-width: $screen-sm-min) {
padding-left: 46px;
@@ -95,6 +96,7 @@
vertical-align: baseline;
}
+
.avatar {
margin-left: -46px;
}
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 1a7d5f9666e..21b1c223c88 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -4,6 +4,11 @@
margin-bottom: $gl-padding;
border-radius: 3px;
+ .commit-short-id {
+ font-family: $regular_font;
+ font-weight: 400;
+ }
+
.diff-header {
position: relative;
background: $background-color;
@@ -429,13 +434,3 @@
}
}
}
-
-.discussion {
- .diff-content {
- .diff-line-num {
- &:before {
- content: attr(data-linenumber);
- }
- }
- }
-}
diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss
index a34b06f1054..1aa4e06d975 100644
--- a/app/assets/stylesheets/pages/editor.scss
+++ b/app/assets/stylesheets/pages/editor.scss
@@ -60,13 +60,14 @@
.encoding-selector,
.license-selector,
- .gitignore-selector {
+ .gitignore-selector,
+ .gitlab-ci-yml-selector {
display: inline-block;
vertical-align: top;
font-family: $regular_font;
}
- .gitignore-selector, .license-selector {
+ .gitignore-selector, .license-selector, .gitlab-ci-yml-selector {
.dropdown {
line-height: 21px;
}
@@ -76,4 +77,10 @@
width: 220px;
}
}
+
+ .gitlab-ci-yml-selector {
+ .dropdown-menu-toggle {
+ width: 250px;
+ }
+ }
}
diff --git a/app/assets/stylesheets/pages/emojis.scss b/app/assets/stylesheets/pages/emojis.scss
index b731abc7450..f17797b2381 100644
--- a/app/assets/stylesheets/pages/emojis.scss
+++ b/app/assets/stylesheets/pages/emojis.scss
@@ -1,4 +1,4 @@
-.emoji-0023-20E3 { background-position: 0 0; }
+.emoji-0023-20E3 { background-position: 0 0px; }
.emoji-002A-20E3 { background-position: -20px 0; }
.emoji-0030-20E3 { background-position: 0 -20px; }
.emoji-0031-20E3 { background-position: -20px -20px; }
@@ -452,1271 +452,1344 @@
.emoji-1F391 { background-position: -420px -200px; }
.emoji-1F392 { background-position: -420px -220px; }
.emoji-1F393 { background-position: -420px -240px; }
-.emoji-1F394 { background-position: -420px -260px; }
-.emoji-1F395 { background-position: -420px -280px; }
-.emoji-1F396 { background-position: -420px -300px; }
-.emoji-1F397 { background-position: -420px -320px; }
-.emoji-1F398 { background-position: -420px -340px; }
-.emoji-1F399 { background-position: -420px -360px; }
-.emoji-1F39A { background-position: -420px -380px; }
-.emoji-1F39B { background-position: -420px -400px; }
-.emoji-1F39C { background-position: 0 -420px; }
-.emoji-1F39D { background-position: -20px -420px; }
-.emoji-1F39E { background-position: -40px -420px; }
-.emoji-1F39F { background-position: -60px -420px; }
-.emoji-1F3A0 { background-position: -80px -420px; }
-.emoji-1F3A1 { background-position: -100px -420px; }
-.emoji-1F3A2 { background-position: -120px -420px; }
-.emoji-1F3A3 { background-position: -140px -420px; }
-.emoji-1F3A4 { background-position: -160px -420px; }
-.emoji-1F3A5 { background-position: -180px -420px; }
-.emoji-1F3A6 { background-position: -200px -420px; }
-.emoji-1F3A7 { background-position: -220px -420px; }
-.emoji-1F3A8 { background-position: -240px -420px; }
-.emoji-1F3A9 { background-position: -260px -420px; }
-.emoji-1F3AA { background-position: -280px -420px; }
-.emoji-1F3AB { background-position: -300px -420px; }
-.emoji-1F3AC { background-position: -320px -420px; }
-.emoji-1F3AD { background-position: -340px -420px; }
-.emoji-1F3AE { background-position: -360px -420px; }
-.emoji-1F3AF { background-position: -380px -420px; }
-.emoji-1F3B0 { background-position: -400px -420px; }
-.emoji-1F3B1 { background-position: -420px -420px; }
-.emoji-1F3B2 { background-position: -440px 0; }
-.emoji-1F3B3 { background-position: -440px -20px; }
-.emoji-1F3B4 { background-position: -440px -40px; }
-.emoji-1F3B5 { background-position: -440px -60px; }
-.emoji-1F3B6 { background-position: -440px -80px; }
-.emoji-1F3B7 { background-position: -440px -100px; }
-.emoji-1F3B8 { background-position: -440px -120px; }
-.emoji-1F3B9 { background-position: -440px -140px; }
-.emoji-1F3BA { background-position: -440px -160px; }
-.emoji-1F3BB { background-position: -440px -180px; }
-.emoji-1F3BC { background-position: -440px -200px; }
-.emoji-1F3BD { background-position: -440px -220px; }
-.emoji-1F3BE { background-position: -440px -240px; }
-.emoji-1F3BF { background-position: -440px -260px; }
-.emoji-1F3C0 { background-position: -440px -280px; }
-.emoji-1F3C1 { background-position: -440px -300px; }
-.emoji-1F3C2 { background-position: -440px -320px; }
-.emoji-1F3C3 { background-position: -440px -340px; }
-.emoji-1F3C3-1F3FB { background-position: -440px -360px; }
-.emoji-1F3C3-1F3FC { background-position: -440px -380px; }
-.emoji-1F3C3-1F3FD { background-position: -440px -400px; }
-.emoji-1F3C3-1F3FE { background-position: -440px -420px; }
-.emoji-1F3C3-1F3FF { background-position: 0 -440px; }
-.emoji-1F3C4 { background-position: -20px -440px; }
-.emoji-1F3C4-1F3FB { background-position: -40px -440px; }
-.emoji-1F3C4-1F3FC { background-position: -60px -440px; }
-.emoji-1F3C4-1F3FD { background-position: -80px -440px; }
-.emoji-1F3C4-1F3FE { background-position: -100px -440px; }
-.emoji-1F3C4-1F3FF { background-position: -120px -440px; }
-.emoji-1F3C5 { background-position: -140px -440px; }
-.emoji-1F3C6 { background-position: -160px -440px; }
-.emoji-1F3C7 { background-position: -180px -440px; }
-.emoji-1F3C7-1F3FB { background-position: -200px -440px; }
-.emoji-1F3C7-1F3FC { background-position: -220px -440px; }
-.emoji-1F3C7-1F3FD { background-position: -240px -440px; }
-.emoji-1F3C7-1F3FE { background-position: -260px -440px; }
-.emoji-1F3C7-1F3FF { background-position: -280px -440px; }
-.emoji-1F3C8 { background-position: -300px -440px; }
-.emoji-1F3C9 { background-position: -320px -440px; }
-.emoji-1F3CA { background-position: -340px -440px; }
-.emoji-1F3CA-1F3FB { background-position: -360px -440px; }
-.emoji-1F3CA-1F3FC { background-position: -380px -440px; }
-.emoji-1F3CA-1F3FD { background-position: -400px -440px; }
-.emoji-1F3CA-1F3FE { background-position: -420px -440px; }
-.emoji-1F3CA-1F3FF { background-position: -440px -440px; }
-.emoji-1F3CB { background-position: -460px 0; }
-.emoji-1F3CB-1F3FB { background-position: -460px -20px; }
-.emoji-1F3CB-1F3FC { background-position: -460px -40px; }
-.emoji-1F3CB-1F3FD { background-position: -460px -60px; }
-.emoji-1F3CB-1F3FE { background-position: -460px -80px; }
-.emoji-1F3CB-1F3FF { background-position: -460px -100px; }
-.emoji-1F3CC { background-position: -460px -120px; }
-.emoji-1F3CD { background-position: -460px -140px; }
-.emoji-1F3CE { background-position: -460px -160px; }
-.emoji-1F3CF { background-position: -460px -180px; }
-.emoji-1F3D0 { background-position: -460px -200px; }
-.emoji-1F3D1 { background-position: -460px -220px; }
-.emoji-1F3D2 { background-position: -460px -240px; }
-.emoji-1F3D3 { background-position: -460px -260px; }
-.emoji-1F3D4 { background-position: -460px -280px; }
-.emoji-1F3D5 { background-position: -460px -300px; }
-.emoji-1F3D6 { background-position: -460px -320px; }
-.emoji-1F3D7 { background-position: -460px -340px; }
-.emoji-1F3D8 { background-position: -460px -360px; }
-.emoji-1F3D9 { background-position: -460px -380px; }
-.emoji-1F3DA { background-position: -460px -400px; }
-.emoji-1F3DB { background-position: -460px -420px; }
-.emoji-1F3DC { background-position: -460px -440px; }
-.emoji-1F3DD { background-position: 0 -460px; }
-.emoji-1F3DE { background-position: -20px -460px; }
-.emoji-1F3DF { background-position: -40px -460px; }
-.emoji-1F3E0 { background-position: -60px -460px; }
-.emoji-1F3E1 { background-position: -80px -460px; }
-.emoji-1F3E2 { background-position: -100px -460px; }
-.emoji-1F3E3 { background-position: -120px -460px; }
-.emoji-1F3E4 { background-position: -140px -460px; }
-.emoji-1F3E5 { background-position: -160px -460px; }
-.emoji-1F3E6 { background-position: -180px -460px; }
-.emoji-1F3E7 { background-position: -200px -460px; }
-.emoji-1F3E8 { background-position: -220px -460px; }
-.emoji-1F3E9 { background-position: -240px -460px; }
-.emoji-1F3EA { background-position: -260px -460px; }
-.emoji-1F3EB { background-position: -280px -460px; }
-.emoji-1F3EC { background-position: -300px -460px; }
-.emoji-1F3ED { background-position: -320px -460px; }
-.emoji-1F3EE { background-position: -340px -460px; }
-.emoji-1F3EF { background-position: -360px -460px; }
-.emoji-1F3F0 { background-position: -380px -460px; }
-.emoji-1F3F1 { background-position: -400px -460px; }
-.emoji-1F3F2 { background-position: -420px -460px; }
-.emoji-1F3F3 { background-position: -440px -460px; }
-.emoji-1F3F4 { background-position: -460px -460px; }
-.emoji-1F3F5 { background-position: -480px 0; }
-.emoji-1F3F6 { background-position: -480px -20px; }
-.emoji-1F3F7 { background-position: -480px -40px; }
-.emoji-1F3F8 { background-position: -480px -60px; }
-.emoji-1F3F9 { background-position: -480px -80px; }
-.emoji-1F3FA { background-position: -480px -100px; }
-.emoji-1F3FB { background-position: -480px -120px; }
-.emoji-1F3FC { background-position: -480px -140px; }
-.emoji-1F3FD { background-position: -480px -160px; }
-.emoji-1F3FE { background-position: -480px -180px; }
-.emoji-1F3FF { background-position: -480px -200px; }
-.emoji-1F400 { background-position: -480px -220px; }
-.emoji-1F401 { background-position: -480px -240px; }
-.emoji-1F402 { background-position: -480px -260px; }
-.emoji-1F403 { background-position: -480px -280px; }
-.emoji-1F404 { background-position: -480px -300px; }
-.emoji-1F405 { background-position: -480px -320px; }
-.emoji-1F406 { background-position: -480px -340px; }
-.emoji-1F407 { background-position: -480px -360px; }
-.emoji-1F408 { background-position: -480px -380px; }
-.emoji-1F409 { background-position: -480px -400px; }
-.emoji-1F40A { background-position: -480px -420px; }
-.emoji-1F40B { background-position: -480px -440px; }
-.emoji-1F40C { background-position: -480px -460px; }
-.emoji-1F40D { background-position: 0 -480px; }
-.emoji-1F40E { background-position: -20px -480px; }
-.emoji-1F40F { background-position: -40px -480px; }
-.emoji-1F410 { background-position: -60px -480px; }
-.emoji-1F411 { background-position: -80px -480px; }
-.emoji-1F412 { background-position: -100px -480px; }
-.emoji-1F413 { background-position: -120px -480px; }
-.emoji-1F414 { background-position: -140px -480px; }
-.emoji-1F415 { background-position: -160px -480px; }
-.emoji-1F416 { background-position: -180px -480px; }
-.emoji-1F417 { background-position: -200px -480px; }
-.emoji-1F418 { background-position: -220px -480px; }
-.emoji-1F419 { background-position: -240px -480px; }
-.emoji-1F41A { background-position: -260px -480px; }
-.emoji-1F41B { background-position: -280px -480px; }
-.emoji-1F41C { background-position: -300px -480px; }
-.emoji-1F41D { background-position: -320px -480px; }
-.emoji-1F41E { background-position: -340px -480px; }
-.emoji-1F41F { background-position: -360px -480px; }
-.emoji-1F420 { background-position: -380px -480px; }
-.emoji-1F421 { background-position: -400px -480px; }
-.emoji-1F422 { background-position: -420px -480px; }
-.emoji-1F423 { background-position: -440px -480px; }
-.emoji-1F424 { background-position: -460px -480px; }
-.emoji-1F425 { background-position: -480px -480px; }
-.emoji-1F426 { background-position: -500px 0; }
-.emoji-1F427 { background-position: -500px -20px; }
-.emoji-1F428 { background-position: -500px -40px; }
-.emoji-1F429 { background-position: -500px -60px; }
-.emoji-1F42A { background-position: -500px -80px; }
-.emoji-1F42B { background-position: -500px -100px; }
-.emoji-1F42C { background-position: -500px -120px; }
-.emoji-1F42D { background-position: -500px -140px; }
-.emoji-1F42E { background-position: -500px -160px; }
-.emoji-1F42F { background-position: -500px -180px; }
-.emoji-1F430 { background-position: -500px -200px; }
-.emoji-1F431 { background-position: -500px -220px; }
-.emoji-1F432 { background-position: -500px -240px; }
-.emoji-1F433 { background-position: -500px -260px; }
-.emoji-1F434 { background-position: -500px -280px; }
-.emoji-1F435 { background-position: -500px -300px; }
-.emoji-1F436 { background-position: -500px -320px; }
-.emoji-1F437 { background-position: -500px -340px; }
-.emoji-1F438 { background-position: -500px -360px; }
-.emoji-1F439 { background-position: -500px -380px; }
-.emoji-1F43A { background-position: -500px -400px; }
-.emoji-1F43B { background-position: -500px -420px; }
-.emoji-1F43C { background-position: -500px -440px; }
-.emoji-1F43D { background-position: -500px -460px; }
-.emoji-1F43E { background-position: -500px -480px; }
-.emoji-1F43F { background-position: 0 -500px; }
-.emoji-1F440 { background-position: -20px -500px; }
-.emoji-1F441 { background-position: -40px -500px; }
-.emoji-1F441-1F5E8 { background-position: -60px -500px; }
-.emoji-1F442 { background-position: -80px -500px; }
-.emoji-1F442-1F3FB { background-position: -100px -500px; }
-.emoji-1F442-1F3FC { background-position: -120px -500px; }
-.emoji-1F442-1F3FD { background-position: -140px -500px; }
-.emoji-1F442-1F3FE { background-position: -160px -500px; }
-.emoji-1F442-1F3FF { background-position: -180px -500px; }
-.emoji-1F443 { background-position: -200px -500px; }
-.emoji-1F443-1F3FB { background-position: -220px -500px; }
-.emoji-1F443-1F3FC { background-position: -240px -500px; }
-.emoji-1F443-1F3FD { background-position: -260px -500px; }
-.emoji-1F443-1F3FE { background-position: -280px -500px; }
-.emoji-1F443-1F3FF { background-position: -300px -500px; }
-.emoji-1F444 { background-position: -320px -500px; }
-.emoji-1F445 { background-position: -340px -500px; }
-.emoji-1F446 { background-position: -360px -500px; }
-.emoji-1F446-1F3FB { background-position: -380px -500px; }
-.emoji-1F446-1F3FC { background-position: -400px -500px; }
-.emoji-1F446-1F3FD { background-position: -420px -500px; }
-.emoji-1F446-1F3FE { background-position: -440px -500px; }
-.emoji-1F446-1F3FF { background-position: -460px -500px; }
-.emoji-1F447 { background-position: -480px -500px; }
-.emoji-1F447-1F3FB { background-position: -500px -500px; }
-.emoji-1F447-1F3FC { background-position: -520px 0; }
-.emoji-1F447-1F3FD { background-position: -520px -20px; }
-.emoji-1F447-1F3FE { background-position: -520px -40px; }
-.emoji-1F447-1F3FF { background-position: -520px -60px; }
-.emoji-1F448 { background-position: -520px -80px; }
-.emoji-1F448-1F3FB { background-position: -520px -100px; }
-.emoji-1F448-1F3FC { background-position: -520px -120px; }
-.emoji-1F448-1F3FD { background-position: -520px -140px; }
-.emoji-1F448-1F3FE { background-position: -520px -160px; }
-.emoji-1F448-1F3FF { background-position: -520px -180px; }
-.emoji-1F449 { background-position: -520px -200px; }
-.emoji-1F449-1F3FB { background-position: -520px -220px; }
-.emoji-1F449-1F3FC { background-position: -520px -240px; }
-.emoji-1F449-1F3FD { background-position: -520px -260px; }
-.emoji-1F449-1F3FE { background-position: -520px -280px; }
-.emoji-1F449-1F3FF { background-position: -520px -300px; }
-.emoji-1F44A { background-position: -520px -320px; }
-.emoji-1F44A-1F3FB { background-position: -520px -340px; }
-.emoji-1F44A-1F3FC { background-position: -520px -360px; }
-.emoji-1F44A-1F3FD { background-position: -520px -380px; }
-.emoji-1F44A-1F3FE { background-position: -520px -400px; }
-.emoji-1F44A-1F3FF { background-position: -520px -420px; }
-.emoji-1F44B { background-position: -520px -440px; }
-.emoji-1F44B-1F3FB { background-position: -520px -460px; }
-.emoji-1F44B-1F3FC { background-position: -520px -480px; }
-.emoji-1F44B-1F3FD { background-position: -520px -500px; }
-.emoji-1F44B-1F3FE { background-position: 0 -520px; }
-.emoji-1F44B-1F3FF { background-position: -20px -520px; }
-.emoji-1F44C { background-position: -40px -520px; }
-.emoji-1F44C-1F3FB { background-position: -60px -520px; }
-.emoji-1F44C-1F3FC { background-position: -80px -520px; }
-.emoji-1F44C-1F3FD { background-position: -100px -520px; }
-.emoji-1F44C-1F3FE { background-position: -120px -520px; }
-.emoji-1F44C-1F3FF { background-position: -140px -520px; }
-.emoji-1F44D { background-position: -160px -520px; }
-.emoji-1F44D-1F3FB { background-position: -180px -520px; }
-.emoji-1F44D-1F3FC { background-position: -200px -520px; }
-.emoji-1F44D-1F3FD { background-position: -220px -520px; }
-.emoji-1F44D-1F3FE { background-position: -240px -520px; }
-.emoji-1F44D-1F3FF { background-position: -260px -520px; }
-.emoji-1F44E { background-position: -280px -520px; }
-.emoji-1F44E-1F3FB { background-position: -300px -520px; }
-.emoji-1F44E-1F3FC { background-position: -320px -520px; }
-.emoji-1F44E-1F3FD { background-position: -340px -520px; }
-.emoji-1F44E-1F3FE { background-position: -360px -520px; }
-.emoji-1F44E-1F3FF { background-position: -380px -520px; }
-.emoji-1F44F { background-position: -400px -520px; }
-.emoji-1F44F-1F3FB { background-position: -420px -520px; }
-.emoji-1F44F-1F3FC { background-position: -440px -520px; }
-.emoji-1F44F-1F3FD { background-position: -460px -520px; }
-.emoji-1F44F-1F3FE { background-position: -480px -520px; }
-.emoji-1F44F-1F3FF { background-position: -500px -520px; }
-.emoji-1F450 { background-position: -520px -520px; }
-.emoji-1F450-1F3FB { background-position: -540px 0; }
-.emoji-1F450-1F3FC { background-position: -540px -20px; }
-.emoji-1F450-1F3FD { background-position: -540px -40px; }
-.emoji-1F450-1F3FE { background-position: -540px -60px; }
-.emoji-1F450-1F3FF { background-position: -540px -80px; }
-.emoji-1F451 { background-position: -540px -100px; }
-.emoji-1F452 { background-position: -540px -120px; }
-.emoji-1F453 { background-position: -540px -140px; }
-.emoji-1F454 { background-position: -540px -160px; }
-.emoji-1F455 { background-position: -540px -180px; }
-.emoji-1F456 { background-position: -540px -200px; }
-.emoji-1F457 { background-position: -540px -220px; }
-.emoji-1F458 { background-position: -540px -240px; }
-.emoji-1F459 { background-position: -540px -260px; }
-.emoji-1F45A { background-position: -540px -280px; }
-.emoji-1F45B { background-position: -540px -300px; }
-.emoji-1F45C { background-position: -540px -320px; }
-.emoji-1F45D { background-position: -540px -340px; }
-.emoji-1F45E { background-position: -540px -360px; }
-.emoji-1F45F { background-position: -540px -380px; }
-.emoji-1F460 { background-position: -540px -400px; }
-.emoji-1F461 { background-position: -540px -420px; }
-.emoji-1F462 { background-position: -540px -440px; }
-.emoji-1F463 { background-position: -540px -460px; }
-.emoji-1F464 { background-position: -540px -480px; }
-.emoji-1F465 { background-position: -540px -500px; }
-.emoji-1F466 { background-position: -540px -520px; }
-.emoji-1F466-1F3FB { background-position: 0 -540px; }
-.emoji-1F466-1F3FC { background-position: -20px -540px; }
-.emoji-1F466-1F3FD { background-position: -40px -540px; }
-.emoji-1F466-1F3FE { background-position: -60px -540px; }
-.emoji-1F466-1F3FF { background-position: -80px -540px; }
-.emoji-1F467 { background-position: -100px -540px; }
-.emoji-1F467-1F3FB { background-position: -120px -540px; }
-.emoji-1F467-1F3FC { background-position: -140px -540px; }
-.emoji-1F467-1F3FD { background-position: -160px -540px; }
-.emoji-1F467-1F3FE { background-position: -180px -540px; }
-.emoji-1F467-1F3FF { background-position: -200px -540px; }
-.emoji-1F468 { background-position: -220px -540px; }
-.emoji-1F468-1F3FB { background-position: -240px -540px; }
-.emoji-1F468-1F3FC { background-position: -260px -540px; }
-.emoji-1F468-1F3FD { background-position: -280px -540px; }
-.emoji-1F468-1F3FE { background-position: -300px -540px; }
-.emoji-1F468-1F3FF { background-position: -320px -540px; }
-.emoji-1F468-1F468-1F466 { background-position: -340px -540px; }
-.emoji-1F468-1F468-1F466-1F466 { background-position: -360px -540px; }
-.emoji-1F468-1F468-1F467 { background-position: -380px -540px; }
-.emoji-1F468-1F468-1F467-1F466 { background-position: -400px -540px; }
-.emoji-1F468-1F468-1F467-1F467 { background-position: -420px -540px; }
-.emoji-1F468-1F469-1F466-1F466 { background-position: -440px -540px; }
-.emoji-1F468-1F469-1F467 { background-position: -460px -540px; }
-.emoji-1F468-1F469-1F467-1F466 { background-position: -480px -540px; }
-.emoji-1F468-1F469-1F467-1F467 { background-position: -500px -540px; }
-.emoji-1F468-2764-1F468 { background-position: -520px -540px; }
-.emoji-1F468-2764-1F48B-1F468 { background-position: -540px -540px; }
-.emoji-1F469 { background-position: -560px 0; }
-.emoji-1F469-1F3FB { background-position: -560px -20px; }
-.emoji-1F469-1F3FC { background-position: -560px -40px; }
-.emoji-1F469-1F3FD { background-position: -560px -60px; }
-.emoji-1F469-1F3FE { background-position: -560px -80px; }
-.emoji-1F469-1F3FF { background-position: -560px -100px; }
-.emoji-1F469-1F469-1F466 { background-position: -560px -120px; }
-.emoji-1F469-1F469-1F466-1F466 { background-position: -560px -140px; }
-.emoji-1F469-1F469-1F467 { background-position: -560px -160px; }
-.emoji-1F469-1F469-1F467-1F466 { background-position: -560px -180px; }
-.emoji-1F469-1F469-1F467-1F467 { background-position: -560px -200px; }
-.emoji-1F469-2764-1F469 { background-position: -560px -220px; }
-.emoji-1F469-2764-1F48B-1F469 { background-position: -560px -240px; }
-.emoji-1F46A { background-position: -560px -260px; }
-.emoji-1F46B { background-position: -560px -280px; }
-.emoji-1F46C { background-position: -560px -300px; }
-.emoji-1F46D { background-position: -560px -320px; }
-.emoji-1F46E { background-position: -560px -340px; }
-.emoji-1F46E-1F3FB { background-position: -560px -360px; }
-.emoji-1F46E-1F3FC { background-position: -560px -380px; }
-.emoji-1F46E-1F3FD { background-position: -560px -400px; }
-.emoji-1F46E-1F3FE { background-position: -560px -420px; }
-.emoji-1F46E-1F3FF { background-position: -560px -440px; }
-.emoji-1F46F { background-position: -560px -460px; }
-.emoji-1F470 { background-position: -560px -480px; }
-.emoji-1F470-1F3FB { background-position: -560px -500px; }
-.emoji-1F470-1F3FC { background-position: -560px -520px; }
-.emoji-1F470-1F3FD { background-position: -560px -540px; }
-.emoji-1F470-1F3FE { background-position: 0 -560px; }
-.emoji-1F470-1F3FF { background-position: -20px -560px; }
-.emoji-1F471 { background-position: -40px -560px; }
-.emoji-1F471-1F3FB { background-position: -60px -560px; }
-.emoji-1F471-1F3FC { background-position: -80px -560px; }
-.emoji-1F471-1F3FD { background-position: -100px -560px; }
-.emoji-1F471-1F3FE { background-position: -120px -560px; }
-.emoji-1F471-1F3FF { background-position: -140px -560px; }
-.emoji-1F472 { background-position: -160px -560px; }
-.emoji-1F472-1F3FB { background-position: -180px -560px; }
-.emoji-1F472-1F3FC { background-position: -200px -560px; }
-.emoji-1F472-1F3FD { background-position: -220px -560px; }
-.emoji-1F472-1F3FE { background-position: -240px -560px; }
-.emoji-1F472-1F3FF { background-position: -260px -560px; }
-.emoji-1F473 { background-position: -280px -560px; }
-.emoji-1F473-1F3FB { background-position: -300px -560px; }
-.emoji-1F473-1F3FC { background-position: -320px -560px; }
-.emoji-1F473-1F3FD { background-position: -340px -560px; }
-.emoji-1F473-1F3FE { background-position: -360px -560px; }
-.emoji-1F473-1F3FF { background-position: -380px -560px; }
-.emoji-1F474 { background-position: -400px -560px; }
-.emoji-1F474-1F3FB { background-position: -420px -560px; }
-.emoji-1F474-1F3FC { background-position: -440px -560px; }
-.emoji-1F474-1F3FD { background-position: -460px -560px; }
-.emoji-1F474-1F3FE { background-position: -480px -560px; }
-.emoji-1F474-1F3FF { background-position: -500px -560px; }
-.emoji-1F475 { background-position: -520px -560px; }
-.emoji-1F475-1F3FB { background-position: -540px -560px; }
-.emoji-1F475-1F3FC { background-position: -560px -560px; }
-.emoji-1F475-1F3FD { background-position: -580px 0; }
-.emoji-1F475-1F3FE { background-position: -580px -20px; }
-.emoji-1F475-1F3FF { background-position: -580px -40px; }
-.emoji-1F476 { background-position: -580px -60px; }
-.emoji-1F476-1F3FB { background-position: -580px -80px; }
-.emoji-1F476-1F3FC { background-position: -580px -100px; }
-.emoji-1F476-1F3FD { background-position: -580px -120px; }
-.emoji-1F476-1F3FE { background-position: -580px -140px; }
-.emoji-1F476-1F3FF { background-position: -580px -160px; }
-.emoji-1F477 { background-position: -580px -180px; }
-.emoji-1F477-1F3FB { background-position: -580px -200px; }
-.emoji-1F477-1F3FC { background-position: -580px -220px; }
-.emoji-1F477-1F3FD { background-position: -580px -240px; }
-.emoji-1F477-1F3FE { background-position: -580px -260px; }
-.emoji-1F477-1F3FF { background-position: -580px -280px; }
-.emoji-1F478 { background-position: -580px -300px; }
-.emoji-1F478-1F3FB { background-position: -580px -320px; }
-.emoji-1F478-1F3FC { background-position: -580px -340px; }
-.emoji-1F478-1F3FD { background-position: -580px -360px; }
-.emoji-1F478-1F3FE { background-position: -580px -380px; }
-.emoji-1F478-1F3FF { background-position: -580px -400px; }
-.emoji-1F479 { background-position: -580px -420px; }
-.emoji-1F47A { background-position: -580px -440px; }
-.emoji-1F47B { background-position: -580px -460px; }
-.emoji-1F47C { background-position: -580px -480px; }
-.emoji-1F47C-1F3FB { background-position: -580px -500px; }
-.emoji-1F47C-1F3FC { background-position: -580px -520px; }
-.emoji-1F47C-1F3FD { background-position: -580px -540px; }
-.emoji-1F47C-1F3FE { background-position: -580px -560px; }
-.emoji-1F47C-1F3FF { background-position: 0 -580px; }
-.emoji-1F47D { background-position: -20px -580px; }
-.emoji-1F47E { background-position: -40px -580px; }
-.emoji-1F47F { background-position: -60px -580px; }
-.emoji-1F480 { background-position: -80px -580px; }
-.emoji-1F481 { background-position: -100px -580px; }
-.emoji-1F481-1F3FB { background-position: -120px -580px; }
-.emoji-1F481-1F3FC { background-position: -140px -580px; }
-.emoji-1F481-1F3FD { background-position: -160px -580px; }
-.emoji-1F481-1F3FE { background-position: -180px -580px; }
-.emoji-1F481-1F3FF { background-position: -200px -580px; }
-.emoji-1F482 { background-position: -220px -580px; }
-.emoji-1F482-1F3FB { background-position: -240px -580px; }
-.emoji-1F482-1F3FC { background-position: -260px -580px; }
-.emoji-1F482-1F3FD { background-position: -280px -580px; }
-.emoji-1F482-1F3FE { background-position: -300px -580px; }
-.emoji-1F482-1F3FF { background-position: -320px -580px; }
-.emoji-1F483 { background-position: -340px -580px; }
-.emoji-1F483-1F3FB { background-position: -360px -580px; }
-.emoji-1F483-1F3FC { background-position: -380px -580px; }
-.emoji-1F483-1F3FD { background-position: -400px -580px; }
-.emoji-1F483-1F3FE { background-position: -420px -580px; }
-.emoji-1F483-1F3FF { background-position: -440px -580px; }
-.emoji-1F484 { background-position: -460px -580px; }
-.emoji-1F485 { background-position: -480px -580px; }
-.emoji-1F485-1F3FB { background-position: -500px -580px; }
-.emoji-1F485-1F3FC { background-position: -520px -580px; }
-.emoji-1F485-1F3FD { background-position: -540px -580px; }
-.emoji-1F485-1F3FE { background-position: -560px -580px; }
-.emoji-1F485-1F3FF { background-position: -580px -580px; }
-.emoji-1F486 { background-position: -600px 0; }
-.emoji-1F486-1F3FB { background-position: -600px -20px; }
-.emoji-1F486-1F3FC { background-position: -600px -40px; }
-.emoji-1F486-1F3FD { background-position: -600px -60px; }
-.emoji-1F486-1F3FE { background-position: -600px -80px; }
-.emoji-1F486-1F3FF { background-position: -600px -100px; }
-.emoji-1F487 { background-position: -600px -120px; }
-.emoji-1F487-1F3FB { background-position: -600px -140px; }
-.emoji-1F487-1F3FC { background-position: -600px -160px; }
-.emoji-1F487-1F3FD { background-position: -600px -180px; }
-.emoji-1F487-1F3FE { background-position: -600px -200px; }
-.emoji-1F487-1F3FF { background-position: -600px -220px; }
-.emoji-1F488 { background-position: -600px -240px; }
-.emoji-1F489 { background-position: -600px -260px; }
-.emoji-1F48A { background-position: -600px -280px; }
-.emoji-1F48B { background-position: -600px -300px; }
-.emoji-1F48C { background-position: -600px -320px; }
-.emoji-1F48D { background-position: -600px -340px; }
-.emoji-1F48E { background-position: -600px -360px; }
-.emoji-1F48F { background-position: -600px -380px; }
-.emoji-1F490 { background-position: -600px -400px; }
-.emoji-1F491 { background-position: -600px -420px; }
-.emoji-1F492 { background-position: -600px -440px; }
-.emoji-1F493 { background-position: -600px -460px; }
-.emoji-1F494 { background-position: -600px -480px; }
-.emoji-1F495 { background-position: -600px -500px; }
-.emoji-1F496 { background-position: -600px -520px; }
-.emoji-1F497 { background-position: -600px -540px; }
-.emoji-1F498 { background-position: -600px -560px; }
-.emoji-1F499 { background-position: -600px -580px; }
-.emoji-1F49A { background-position: 0 -600px; }
-.emoji-1F49B { background-position: -20px -600px; }
-.emoji-1F49C { background-position: -40px -600px; }
-.emoji-1F49D { background-position: -60px -600px; }
-.emoji-1F49E { background-position: -80px -600px; }
-.emoji-1F49F { background-position: -100px -600px; }
-.emoji-1F4A0 { background-position: -120px -600px; }
-.emoji-1F4A1 { background-position: -140px -600px; }
-.emoji-1F4A2 { background-position: -160px -600px; }
-.emoji-1F4A3 { background-position: -180px -600px; }
-.emoji-1F4A4 { background-position: -200px -600px; }
-.emoji-1F4A5 { background-position: -220px -600px; }
-.emoji-1F4A6 { background-position: -240px -600px; }
-.emoji-1F4A7 { background-position: -260px -600px; }
-.emoji-1F4A8 { background-position: -280px -600px; }
-.emoji-1F4A9 { background-position: -300px -600px; }
-.emoji-1F4AA { background-position: -320px -600px; }
-.emoji-1F4AA-1F3FB { background-position: -340px -600px; }
-.emoji-1F4AA-1F3FC { background-position: -360px -600px; }
-.emoji-1F4AA-1F3FD { background-position: -380px -600px; }
-.emoji-1F4AA-1F3FE { background-position: -400px -600px; }
-.emoji-1F4AA-1F3FF { background-position: -420px -600px; }
-.emoji-1F4AB { background-position: -440px -600px; }
-.emoji-1F4AC { background-position: -460px -600px; }
-.emoji-1F4AD { background-position: -480px -600px; }
-.emoji-1F4AE { background-position: -500px -600px; }
-.emoji-1F4AF { background-position: -520px -600px; }
-.emoji-1F4B0 { background-position: -540px -600px; }
-.emoji-1F4B1 { background-position: -560px -600px; }
-.emoji-1F4B2 { background-position: -580px -600px; }
-.emoji-1F4B3 { background-position: -600px -600px; }
-.emoji-1F4B4 { background-position: -620px 0; }
-.emoji-1F4B5 { background-position: -620px -20px; }
-.emoji-1F4B6 { background-position: -620px -40px; }
-.emoji-1F4B7 { background-position: -620px -60px; }
-.emoji-1F4B8 { background-position: -620px -80px; }
-.emoji-1F4B9 { background-position: -620px -100px; }
-.emoji-1F4BA { background-position: -620px -120px; }
-.emoji-1F4BB { background-position: -620px -140px; }
-.emoji-1F4BC { background-position: -620px -160px; }
-.emoji-1F4BD { background-position: -620px -180px; }
-.emoji-1F4BE { background-position: -620px -200px; }
-.emoji-1F4BF { background-position: -620px -220px; }
-.emoji-1F4C0 { background-position: -620px -240px; }
-.emoji-1F4C1 { background-position: -620px -260px; }
-.emoji-1F4C2 { background-position: -620px -280px; }
-.emoji-1F4C3 { background-position: -620px -300px; }
-.emoji-1F4C4 { background-position: -620px -320px; }
-.emoji-1F4C5 { background-position: -620px -340px; }
-.emoji-1F4C6 { background-position: -620px -360px; }
-.emoji-1F4C7 { background-position: -620px -380px; }
-.emoji-1F4C8 { background-position: -620px -400px; }
-.emoji-1F4C9 { background-position: -620px -420px; }
-.emoji-1F4CA { background-position: -620px -440px; }
-.emoji-1F4CB { background-position: -620px -460px; }
-.emoji-1F4CC { background-position: -620px -480px; }
-.emoji-1F4CD { background-position: -620px -500px; }
-.emoji-1F4CE { background-position: -620px -520px; }
-.emoji-1F4CF { background-position: -620px -540px; }
-.emoji-1F4D0 { background-position: -620px -560px; }
-.emoji-1F4D1 { background-position: -620px -580px; }
-.emoji-1F4D2 { background-position: -620px -600px; }
-.emoji-1F4D3 { background-position: 0 -620px; }
-.emoji-1F4D4 { background-position: -20px -620px; }
-.emoji-1F4D5 { background-position: -40px -620px; }
-.emoji-1F4D6 { background-position: -60px -620px; }
-.emoji-1F4D7 { background-position: -80px -620px; }
-.emoji-1F4D8 { background-position: -100px -620px; }
-.emoji-1F4D9 { background-position: -120px -620px; }
-.emoji-1F4DA { background-position: -140px -620px; }
-.emoji-1F4DB { background-position: -160px -620px; }
-.emoji-1F4DC { background-position: -180px -620px; }
-.emoji-1F4DD { background-position: -200px -620px; }
-.emoji-1F4DE { background-position: -220px -620px; }
-.emoji-1F4DF { background-position: -240px -620px; }
-.emoji-1F4E0 { background-position: -260px -620px; }
-.emoji-1F4E1 { background-position: -280px -620px; }
-.emoji-1F4E2 { background-position: -300px -620px; }
-.emoji-1F4E3 { background-position: -320px -620px; }
-.emoji-1F4E4 { background-position: -340px -620px; }
-.emoji-1F4E5 { background-position: -360px -620px; }
-.emoji-1F4E6 { background-position: -380px -620px; }
-.emoji-1F4E7 { background-position: -400px -620px; }
-.emoji-1F4E8 { background-position: -420px -620px; }
-.emoji-1F4E9 { background-position: -440px -620px; }
-.emoji-1F4EA { background-position: -460px -620px; }
-.emoji-1F4EB { background-position: -480px -620px; }
-.emoji-1F4EC { background-position: -500px -620px; }
-.emoji-1F4ED { background-position: -520px -620px; }
-.emoji-1F4EE { background-position: -540px -620px; }
-.emoji-1F4EF { background-position: -560px -620px; }
-.emoji-1F4F0 { background-position: -580px -620px; }
-.emoji-1F4F1 { background-position: -600px -620px; }
-.emoji-1F4F2 { background-position: -620px -620px; }
-.emoji-1F4F3 { background-position: -640px 0; }
-.emoji-1F4F4 { background-position: -640px -20px; }
-.emoji-1F4F5 { background-position: -640px -40px; }
-.emoji-1F4F6 { background-position: -640px -60px; }
-.emoji-1F4F7 { background-position: -640px -80px; }
-.emoji-1F4F8 { background-position: -640px -100px; }
-.emoji-1F4F9 { background-position: -640px -120px; }
-.emoji-1F4FA { background-position: -640px -140px; }
-.emoji-1F4FB { background-position: -640px -160px; }
-.emoji-1F4FC { background-position: -640px -180px; }
-.emoji-1F4FD { background-position: -640px -200px; }
-.emoji-1F4FE { background-position: -640px -220px; }
-.emoji-1F4FF { background-position: -640px -240px; }
-.emoji-1F500 { background-position: -640px -260px; }
-.emoji-1F501 { background-position: -640px -280px; }
-.emoji-1F502 { background-position: -640px -300px; }
-.emoji-1F503 { background-position: -640px -320px; }
-.emoji-1F504 { background-position: -640px -340px; }
-.emoji-1F505 { background-position: -640px -360px; }
-.emoji-1F506 { background-position: -640px -380px; }
-.emoji-1F507 { background-position: -640px -400px; }
-.emoji-1F508 { background-position: -640px -420px; }
-.emoji-1F509 { background-position: -640px -440px; }
-.emoji-1F50A { background-position: -640px -460px; }
-.emoji-1F50B { background-position: -640px -480px; }
-.emoji-1F50C { background-position: -640px -500px; }
-.emoji-1F50D { background-position: -640px -520px; }
-.emoji-1F50E { background-position: -640px -540px; }
-.emoji-1F50F { background-position: -640px -560px; }
-.emoji-1F510 { background-position: -640px -580px; }
-.emoji-1F511 { background-position: -640px -600px; }
-.emoji-1F512 { background-position: -640px -620px; }
-.emoji-1F513 { background-position: 0 -640px; }
-.emoji-1F514 { background-position: -20px -640px; }
-.emoji-1F515 { background-position: -40px -640px; }
-.emoji-1F516 { background-position: -60px -640px; }
-.emoji-1F517 { background-position: -80px -640px; }
-.emoji-1F518 { background-position: -100px -640px; }
-.emoji-1F519 { background-position: -120px -640px; }
-.emoji-1F51A { background-position: -140px -640px; }
-.emoji-1F51B { background-position: -160px -640px; }
-.emoji-1F51C { background-position: -180px -640px; }
-.emoji-1F51D { background-position: -200px -640px; }
-.emoji-1F51E { background-position: -220px -640px; }
-.emoji-1F51F { background-position: -240px -640px; }
-.emoji-1F520 { background-position: -260px -640px; }
-.emoji-1F521 { background-position: -280px -640px; }
-.emoji-1F522 { background-position: -300px -640px; }
-.emoji-1F523 { background-position: -320px -640px; }
-.emoji-1F524 { background-position: -340px -640px; }
-.emoji-1F525 { background-position: -360px -640px; }
-.emoji-1F526 { background-position: -380px -640px; }
-.emoji-1F527 { background-position: -400px -640px; }
-.emoji-1F528 { background-position: -420px -640px; }
-.emoji-1F529 { background-position: -440px -640px; }
-.emoji-1F52A { background-position: -460px -640px; }
-.emoji-1F52B { background-position: -480px -640px; }
-.emoji-1F52C { background-position: -500px -640px; }
-.emoji-1F52D { background-position: -520px -640px; }
-.emoji-1F52E { background-position: -540px -640px; }
-.emoji-1F52F { background-position: -560px -640px; }
-.emoji-1F530 { background-position: -580px -640px; }
-.emoji-1F531 { background-position: -600px -640px; }
-.emoji-1F532 { background-position: -620px -640px; }
-.emoji-1F533 { background-position: -640px -640px; }
-.emoji-1F534 { background-position: -660px 0; }
-.emoji-1F535 { background-position: -660px -20px; }
-.emoji-1F536 { background-position: -660px -40px; }
-.emoji-1F537 { background-position: -660px -60px; }
-.emoji-1F538 { background-position: -660px -80px; }
-.emoji-1F539 { background-position: -660px -100px; }
-.emoji-1F53A { background-position: -660px -120px; }
-.emoji-1F53B { background-position: -660px -140px; }
-.emoji-1F53C { background-position: -660px -160px; }
-.emoji-1F53D { background-position: -660px -180px; }
-.emoji-1F546 { background-position: -660px -200px; }
-.emoji-1F547 { background-position: -660px -220px; }
-.emoji-1F548 { background-position: -660px -240px; }
-.emoji-1F549 { background-position: -660px -260px; }
-.emoji-1F54A { background-position: -660px -280px; }
-.emoji-1F54B { background-position: -660px -300px; }
-.emoji-1F54C { background-position: -660px -320px; }
-.emoji-1F54D { background-position: -660px -340px; }
-.emoji-1F54E { background-position: -660px -360px; }
-.emoji-1F550 { background-position: -660px -380px; }
-.emoji-1F551 { background-position: -660px -400px; }
-.emoji-1F552 { background-position: -660px -420px; }
-.emoji-1F553 { background-position: -660px -440px; }
-.emoji-1F554 { background-position: -660px -460px; }
-.emoji-1F555 { background-position: -660px -480px; }
-.emoji-1F556 { background-position: -660px -500px; }
-.emoji-1F557 { background-position: -660px -520px; }
-.emoji-1F558 { background-position: -660px -540px; }
-.emoji-1F559 { background-position: -660px -560px; }
-.emoji-1F55A { background-position: -660px -580px; }
-.emoji-1F55B { background-position: -660px -600px; }
-.emoji-1F55C { background-position: -660px -620px; }
-.emoji-1F55D { background-position: -660px -640px; }
-.emoji-1F55E { background-position: 0 -660px; }
-.emoji-1F55F { background-position: -20px -660px; }
-.emoji-1F560 { background-position: -40px -660px; }
-.emoji-1F561 { background-position: -60px -660px; }
-.emoji-1F562 { background-position: -80px -660px; }
-.emoji-1F563 { background-position: -100px -660px; }
-.emoji-1F564 { background-position: -120px -660px; }
-.emoji-1F565 { background-position: -140px -660px; }
-.emoji-1F566 { background-position: -160px -660px; }
-.emoji-1F567 { background-position: -180px -660px; }
-.emoji-1F568 { background-position: -200px -660px; }
-.emoji-1F569 { background-position: -220px -660px; }
-.emoji-1F56A { background-position: -240px -660px; }
-.emoji-1F56B { background-position: -260px -660px; }
-.emoji-1F56C { background-position: -280px -660px; }
-.emoji-1F56D { background-position: -300px -660px; }
-.emoji-1F56E { background-position: -320px -660px; }
-.emoji-1F56F { background-position: -340px -660px; }
-.emoji-1F570 { background-position: -360px -660px; }
-.emoji-1F571 { background-position: -380px -660px; }
-.emoji-1F572 { background-position: -400px -660px; }
-.emoji-1F573 { background-position: -420px -660px; }
-.emoji-1F574 { background-position: -440px -660px; }
-.emoji-1F575 { background-position: -460px -660px; }
-.emoji-1F575-1F3FB { background-position: -480px -660px; }
-.emoji-1F575-1F3FC { background-position: -500px -660px; }
-.emoji-1F575-1F3FD { background-position: -520px -660px; }
-.emoji-1F575-1F3FE { background-position: -540px -660px; }
-.emoji-1F575-1F3FF { background-position: -560px -660px; }
-.emoji-1F576 { background-position: -580px -660px; }
-.emoji-1F577 { background-position: -600px -660px; }
-.emoji-1F578 { background-position: -620px -660px; }
-.emoji-1F579 { background-position: -640px -660px; }
-.emoji-1F57B { background-position: -660px -660px; }
-.emoji-1F57E { background-position: -680px 0; }
-.emoji-1F57F { background-position: -680px -20px; }
-.emoji-1F581 { background-position: -680px -40px; }
-.emoji-1F582 { background-position: -680px -60px; }
-.emoji-1F583 { background-position: -680px -80px; }
-.emoji-1F585 { background-position: -680px -100px; }
-.emoji-1F586 { background-position: -680px -120px; }
-.emoji-1F587 { background-position: -680px -140px; }
-.emoji-1F588 { background-position: -680px -160px; }
-.emoji-1F589 { background-position: -680px -180px; }
-.emoji-1F58A { background-position: -680px -200px; }
-.emoji-1F58B { background-position: -680px -220px; }
-.emoji-1F58C { background-position: -680px -240px; }
-.emoji-1F58D { background-position: -680px -260px; }
-.emoji-1F58E { background-position: -680px -280px; }
-.emoji-1F58F { background-position: -680px -300px; }
-.emoji-1F590 { background-position: -680px -320px; }
-.emoji-1F590-1F3FB { background-position: -680px -340px; }
-.emoji-1F590-1F3FC { background-position: -680px -360px; }
-.emoji-1F590-1F3FD { background-position: -680px -380px; }
-.emoji-1F590-1F3FE { background-position: -680px -400px; }
-.emoji-1F590-1F3FF { background-position: -680px -420px; }
-.emoji-1F591 { background-position: -680px -440px; }
-.emoji-1F592 { background-position: -680px -460px; }
-.emoji-1F593 { background-position: -680px -480px; }
-.emoji-1F594 { background-position: -680px -500px; }
-.emoji-1F595 { background-position: -680px -520px; }
-.emoji-1F595-1F3FB { background-position: -680px -540px; }
-.emoji-1F595-1F3FC { background-position: -680px -560px; }
-.emoji-1F595-1F3FD { background-position: -680px -580px; }
-.emoji-1F595-1F3FE { background-position: -680px -600px; }
-.emoji-1F595-1F3FF { background-position: -680px -620px; }
-.emoji-1F596 { background-position: -680px -640px; }
-.emoji-1F596-1F3FB { background-position: -680px -660px; }
-.emoji-1F596-1F3FC { background-position: 0 -680px; }
-.emoji-1F596-1F3FD { background-position: -20px -680px; }
-.emoji-1F596-1F3FE { background-position: -40px -680px; }
-.emoji-1F596-1F3FF { background-position: -60px -680px; }
-.emoji-1F597 { background-position: -80px -680px; }
-.emoji-1F598 { background-position: -100px -680px; }
-.emoji-1F599 { background-position: -120px -680px; }
-.emoji-1F59E { background-position: -140px -680px; }
-.emoji-1F59F { background-position: -160px -680px; }
-.emoji-1F5A5 { background-position: -180px -680px; }
-.emoji-1F5A6 { background-position: -200px -680px; }
-.emoji-1F5A7 { background-position: -220px -680px; }
-.emoji-1F5A8 { background-position: -240px -680px; }
-.emoji-1F5A9 { background-position: -260px -680px; }
-.emoji-1F5AA { background-position: -280px -680px; }
-.emoji-1F5AB { background-position: -300px -680px; }
-.emoji-1F5AD { background-position: -320px -680px; }
-.emoji-1F5AE { background-position: -340px -680px; }
-.emoji-1F5AF { background-position: -360px -680px; }
-.emoji-1F5B1 { background-position: -380px -680px; }
-.emoji-1F5B2 { background-position: -400px -680px; }
-.emoji-1F5B3 { background-position: -420px -680px; }
-.emoji-1F5B4 { background-position: -440px -680px; }
-.emoji-1F5B8 { background-position: -460px -680px; }
-.emoji-1F5B9 { background-position: -480px -680px; }
-.emoji-1F5BC { background-position: -500px -680px; }
-.emoji-1F5BD { background-position: -520px -680px; }
-.emoji-1F5BE { background-position: -540px -680px; }
-.emoji-1F5C0 { background-position: -560px -680px; }
-.emoji-1F5C1 { background-position: -580px -680px; }
-.emoji-1F5C2 { background-position: -600px -680px; }
-.emoji-1F5C3 { background-position: -620px -680px; }
-.emoji-1F5C4 { background-position: -640px -680px; }
-.emoji-1F5C6 { background-position: -660px -680px; }
-.emoji-1F5C7 { background-position: -680px -680px; }
-.emoji-1F5C9 { background-position: -700px 0; }
-.emoji-1F5CA { background-position: -700px -20px; }
-.emoji-1F5CE { background-position: -700px -40px; }
-.emoji-1F5CF { background-position: -700px -60px; }
-.emoji-1F5D0 { background-position: -700px -80px; }
-.emoji-1F5D1 { background-position: -700px -100px; }
-.emoji-1F5D2 { background-position: -700px -120px; }
-.emoji-1F5D3 { background-position: -700px -140px; }
-.emoji-1F5D4 { background-position: -700px -160px; }
-.emoji-1F5D8 { background-position: -700px -180px; }
-.emoji-1F5D9 { background-position: -700px -200px; }
-.emoji-1F5DC { background-position: -700px -220px; }
-.emoji-1F5DD { background-position: -700px -240px; }
-.emoji-1F5DE { background-position: -700px -260px; }
-.emoji-1F5E0 { background-position: -700px -280px; }
-.emoji-1F5E1 { background-position: -700px -300px; }
-.emoji-1F5E2 { background-position: -700px -320px; }
-.emoji-1F5E3 { background-position: -700px -340px; }
-.emoji-1F5E8 { background-position: -700px -360px; }
-.emoji-1F5E9 { background-position: -700px -380px; }
-.emoji-1F5EA { background-position: -700px -400px; }
-.emoji-1F5EB { background-position: -700px -420px; }
-.emoji-1F5EC { background-position: -700px -440px; }
-.emoji-1F5ED { background-position: -700px -460px; }
-.emoji-1F5EE { background-position: -700px -480px; }
-.emoji-1F5EF { background-position: -700px -500px; }
-.emoji-1F5F0 { background-position: -700px -520px; }
-.emoji-1F5F1 { background-position: -700px -540px; }
-.emoji-1F5F2 { background-position: -700px -560px; }
-.emoji-1F5F3 { background-position: -700px -580px; }
-.emoji-1F5F4 { background-position: -700px -600px; }
-.emoji-1F5F5 { background-position: -700px -620px; }
-.emoji-1F5F8 { background-position: -700px -640px; }
-.emoji-1F5F9 { background-position: -700px -660px; }
-.emoji-1F5FA { background-position: -700px -680px; }
-.emoji-1F5FB { background-position: 0 -700px; }
-.emoji-1F5FC { background-position: -20px -700px; }
-.emoji-1F5FD { background-position: -40px -700px; }
-.emoji-1F5FE { background-position: -60px -700px; }
-.emoji-1F5FF { background-position: -80px -700px; }
-.emoji-1F600 { background-position: -100px -700px; }
-.emoji-1F601 { background-position: -120px -700px; }
-.emoji-1F602 { background-position: -140px -700px; }
-.emoji-1F603 { background-position: -160px -700px; }
-.emoji-1F604 { background-position: -180px -700px; }
-.emoji-1F605 { background-position: -200px -700px; }
-.emoji-1F606 { background-position: -220px -700px; }
-.emoji-1F607 { background-position: -240px -700px; }
-.emoji-1F608 { background-position: -260px -700px; }
-.emoji-1F609 { background-position: -280px -700px; }
-.emoji-1F60A { background-position: -300px -700px; }
-.emoji-1F60B { background-position: -320px -700px; }
-.emoji-1F60C { background-position: -340px -700px; }
-.emoji-1F60D { background-position: -360px -700px; }
-.emoji-1F60E { background-position: -380px -700px; }
-.emoji-1F60F { background-position: -400px -700px; }
-.emoji-1F610 { background-position: -420px -700px; }
-.emoji-1F611 { background-position: -440px -700px; }
-.emoji-1F612 { background-position: -460px -700px; }
-.emoji-1F613 { background-position: -480px -700px; }
-.emoji-1F614 { background-position: -500px -700px; }
-.emoji-1F615 { background-position: -520px -700px; }
-.emoji-1F616 { background-position: -540px -700px; }
-.emoji-1F617 { background-position: -560px -700px; }
-.emoji-1F618 { background-position: -580px -700px; }
-.emoji-1F619 { background-position: -600px -700px; }
-.emoji-1F61A { background-position: -620px -700px; }
-.emoji-1F61B { background-position: -640px -700px; }
-.emoji-1F61C { background-position: -660px -700px; }
-.emoji-1F61D { background-position: -680px -700px; }
-.emoji-1F61E { background-position: -700px -700px; }
-.emoji-1F61F { background-position: -720px 0; }
-.emoji-1F620 { background-position: -720px -20px; }
-.emoji-1F621 { background-position: -720px -40px; }
-.emoji-1F622 { background-position: -720px -60px; }
-.emoji-1F623 { background-position: -720px -80px; }
-.emoji-1F624 { background-position: -720px -100px; }
-.emoji-1F625 { background-position: -720px -120px; }
-.emoji-1F626 { background-position: -720px -140px; }
-.emoji-1F627 { background-position: -720px -160px; }
-.emoji-1F628 { background-position: -720px -180px; }
-.emoji-1F629 { background-position: -720px -200px; }
-.emoji-1F62A { background-position: -720px -220px; }
-.emoji-1F62B { background-position: -720px -240px; }
-.emoji-1F62C { background-position: -720px -260px; }
-.emoji-1F62D { background-position: -720px -280px; }
-.emoji-1F62E { background-position: -720px -300px; }
-.emoji-1F62F { background-position: -720px -320px; }
-.emoji-1F630 { background-position: -720px -340px; }
-.emoji-1F631 { background-position: -720px -360px; }
-.emoji-1F632 { background-position: -720px -380px; }
-.emoji-1F633 { background-position: -720px -400px; }
-.emoji-1F634 { background-position: -720px -420px; }
-.emoji-1F635 { background-position: -720px -440px; }
-.emoji-1F636 { background-position: -720px -460px; }
-.emoji-1F637 { background-position: -720px -480px; }
-.emoji-1F638 { background-position: -720px -500px; }
-.emoji-1F639 { background-position: -720px -520px; }
-.emoji-1F63A { background-position: -720px -540px; }
-.emoji-1F63B { background-position: -720px -560px; }
-.emoji-1F63C { background-position: -720px -580px; }
-.emoji-1F63D { background-position: -720px -600px; }
-.emoji-1F63E { background-position: -720px -620px; }
-.emoji-1F63F { background-position: -720px -640px; }
-.emoji-1F640 { background-position: -720px -660px; }
-.emoji-1F641 { background-position: -720px -680px; }
-.emoji-1F642 { background-position: -720px -700px; }
-.emoji-1F643 { background-position: 0 -720px; }
-.emoji-1F644 { background-position: -20px -720px; }
-.emoji-1F645 { background-position: -40px -720px; }
-.emoji-1F645-1F3FB { background-position: -60px -720px; }
-.emoji-1F645-1F3FC { background-position: -80px -720px; }
-.emoji-1F645-1F3FD { background-position: -100px -720px; }
-.emoji-1F645-1F3FE { background-position: -120px -720px; }
-.emoji-1F645-1F3FF { background-position: -140px -720px; }
-.emoji-1F646 { background-position: -160px -720px; }
-.emoji-1F646-1F3FB { background-position: -180px -720px; }
-.emoji-1F646-1F3FC { background-position: -200px -720px; }
-.emoji-1F646-1F3FD { background-position: -220px -720px; }
-.emoji-1F646-1F3FE { background-position: -240px -720px; }
-.emoji-1F646-1F3FF { background-position: -260px -720px; }
-.emoji-1F647 { background-position: -280px -720px; }
-.emoji-1F647-1F3FB { background-position: -300px -720px; }
-.emoji-1F647-1F3FC { background-position: -320px -720px; }
-.emoji-1F647-1F3FD { background-position: -340px -720px; }
-.emoji-1F647-1F3FE { background-position: -360px -720px; }
-.emoji-1F647-1F3FF { background-position: -380px -720px; }
-.emoji-1F648 { background-position: -400px -720px; }
-.emoji-1F649 { background-position: -420px -720px; }
-.emoji-1F64A { background-position: -440px -720px; }
-.emoji-1F64B { background-position: -460px -720px; }
-.emoji-1F64B-1F3FB { background-position: -480px -720px; }
-.emoji-1F64B-1F3FC { background-position: -500px -720px; }
-.emoji-1F64B-1F3FD { background-position: -520px -720px; }
-.emoji-1F64B-1F3FE { background-position: -540px -720px; }
-.emoji-1F64B-1F3FF { background-position: -560px -720px; }
-.emoji-1F64C { background-position: -580px -720px; }
-.emoji-1F64C-1F3FB { background-position: -600px -720px; }
-.emoji-1F64C-1F3FC { background-position: -620px -720px; }
-.emoji-1F64C-1F3FD { background-position: -640px -720px; }
-.emoji-1F64C-1F3FE { background-position: -660px -720px; }
-.emoji-1F64C-1F3FF { background-position: -680px -720px; }
-.emoji-1F64D { background-position: -700px -720px; }
-.emoji-1F64D-1F3FB { background-position: -720px -720px; }
-.emoji-1F64D-1F3FC { background-position: -740px 0; }
-.emoji-1F64D-1F3FD { background-position: -740px -20px; }
-.emoji-1F64D-1F3FE { background-position: -740px -40px; }
-.emoji-1F64D-1F3FF { background-position: -740px -60px; }
-.emoji-1F64E { background-position: -740px -80px; }
-.emoji-1F64E-1F3FB { background-position: -740px -100px; }
-.emoji-1F64E-1F3FC { background-position: -740px -120px; }
-.emoji-1F64E-1F3FD { background-position: -740px -140px; }
-.emoji-1F64E-1F3FE { background-position: -740px -160px; }
-.emoji-1F64E-1F3FF { background-position: -740px -180px; }
-.emoji-1F64F { background-position: -740px -200px; }
-.emoji-1F64F-1F3FB { background-position: -740px -220px; }
-.emoji-1F64F-1F3FC { background-position: -740px -240px; }
-.emoji-1F64F-1F3FD { background-position: -740px -260px; }
-.emoji-1F64F-1F3FE { background-position: -740px -280px; }
-.emoji-1F64F-1F3FF { background-position: -740px -300px; }
-.emoji-1F680 { background-position: -740px -320px; }
-.emoji-1F681 { background-position: -740px -340px; }
-.emoji-1F682 { background-position: -740px -360px; }
-.emoji-1F683 { background-position: -740px -380px; }
-.emoji-1F684 { background-position: -740px -400px; }
-.emoji-1F685 { background-position: -740px -420px; }
-.emoji-1F686 { background-position: -740px -440px; }
-.emoji-1F687 { background-position: -740px -460px; }
-.emoji-1F688 { background-position: -740px -480px; }
-.emoji-1F689 { background-position: -740px -500px; }
-.emoji-1F68A { background-position: -740px -520px; }
-.emoji-1F68B { background-position: -740px -540px; }
-.emoji-1F68C { background-position: -740px -560px; }
-.emoji-1F68D { background-position: -740px -580px; }
-.emoji-1F68E { background-position: -740px -600px; }
-.emoji-1F68F { background-position: -740px -620px; }
-.emoji-1F690 { background-position: -740px -640px; }
-.emoji-1F691 { background-position: -740px -660px; }
-.emoji-1F692 { background-position: -740px -680px; }
-.emoji-1F693 { background-position: -740px -700px; }
-.emoji-1F694 { background-position: -740px -720px; }
-.emoji-1F695 { background-position: 0 -740px; }
-.emoji-1F696 { background-position: -20px -740px; }
-.emoji-1F697 { background-position: -40px -740px; }
-.emoji-1F698 { background-position: -60px -740px; }
-.emoji-1F699 { background-position: -80px -740px; }
-.emoji-1F69A { background-position: -100px -740px; }
-.emoji-1F69B { background-position: -120px -740px; }
-.emoji-1F69C { background-position: -140px -740px; }
-.emoji-1F69D { background-position: -160px -740px; }
-.emoji-1F69E { background-position: -180px -740px; }
-.emoji-1F69F { background-position: -200px -740px; }
-.emoji-1F6A0 { background-position: -220px -740px; }
-.emoji-1F6A1 { background-position: -240px -740px; }
-.emoji-1F6A2 { background-position: -260px -740px; }
-.emoji-1F6A3 { background-position: -280px -740px; }
-.emoji-1F6A3-1F3FB { background-position: -300px -740px; }
-.emoji-1F6A3-1F3FC { background-position: -320px -740px; }
-.emoji-1F6A3-1F3FD { background-position: -340px -740px; }
-.emoji-1F6A3-1F3FE { background-position: -360px -740px; }
-.emoji-1F6A3-1F3FF { background-position: -380px -740px; }
-.emoji-1F6A4 { background-position: -400px -740px; }
-.emoji-1F6A5 { background-position: -420px -740px; }
-.emoji-1F6A6 { background-position: -440px -740px; }
-.emoji-1F6A7 { background-position: -460px -740px; }
-.emoji-1F6A8 { background-position: -480px -740px; }
-.emoji-1F6A9 { background-position: -500px -740px; }
-.emoji-1F6AA { background-position: -520px -740px; }
-.emoji-1F6AB { background-position: -540px -740px; }
-.emoji-1F6AC { background-position: -560px -740px; }
-.emoji-1F6AD { background-position: -580px -740px; }
-.emoji-1F6AE { background-position: -600px -740px; }
-.emoji-1F6AF { background-position: -620px -740px; }
-.emoji-1F6B0 { background-position: -640px -740px; }
-.emoji-1F6B1 { background-position: -660px -740px; }
-.emoji-1F6B2 { background-position: -680px -740px; }
-.emoji-1F6B3 { background-position: -700px -740px; }
-.emoji-1F6B4 { background-position: -720px -740px; }
-.emoji-1F6B4-1F3FB { background-position: -740px -740px; }
-.emoji-1F6B4-1F3FC { background-position: -760px 0; }
-.emoji-1F6B4-1F3FD { background-position: -760px -20px; }
-.emoji-1F6B4-1F3FE { background-position: -760px -40px; }
-.emoji-1F6B4-1F3FF { background-position: -760px -60px; }
-.emoji-1F6B5 { background-position: -760px -80px; }
-.emoji-1F6B5-1F3FB { background-position: -760px -100px; }
-.emoji-1F6B5-1F3FC { background-position: -760px -120px; }
-.emoji-1F6B5-1F3FD { background-position: -760px -140px; }
-.emoji-1F6B5-1F3FE { background-position: -760px -160px; }
-.emoji-1F6B5-1F3FF { background-position: -760px -180px; }
-.emoji-1F6B6 { background-position: -760px -200px; }
-.emoji-1F6B6-1F3FB { background-position: -760px -220px; }
-.emoji-1F6B6-1F3FC { background-position: -760px -240px; }
-.emoji-1F6B6-1F3FD { background-position: -760px -260px; }
-.emoji-1F6B6-1F3FE { background-position: -760px -280px; }
-.emoji-1F6B6-1F3FF { background-position: -760px -300px; }
-.emoji-1F6B7 { background-position: -760px -320px; }
-.emoji-1F6B8 { background-position: -760px -340px; }
-.emoji-1F6B9 { background-position: -760px -360px; }
-.emoji-1F6BA { background-position: -760px -380px; }
-.emoji-1F6BB { background-position: -760px -400px; }
-.emoji-1F6BC { background-position: -760px -420px; }
-.emoji-1F6BD { background-position: -760px -440px; }
-.emoji-1F6BE { background-position: -760px -460px; }
-.emoji-1F6BF { background-position: -760px -480px; }
-.emoji-1F6C0 { background-position: -760px -500px; }
-.emoji-1F6C0-1F3FB { background-position: -760px -520px; }
-.emoji-1F6C0-1F3FC { background-position: -760px -540px; }
-.emoji-1F6C0-1F3FD { background-position: -760px -560px; }
-.emoji-1F6C0-1F3FE { background-position: -760px -580px; }
-.emoji-1F6C0-1F3FF { background-position: -760px -600px; }
-.emoji-1F6C1 { background-position: -760px -620px; }
-.emoji-1F6C2 { background-position: -760px -640px; }
-.emoji-1F6C3 { background-position: -760px -660px; }
-.emoji-1F6C4 { background-position: -760px -680px; }
-.emoji-1F6C5 { background-position: -760px -700px; }
-.emoji-1F6C6 { background-position: -760px -720px; }
-.emoji-1F6C7 { background-position: -760px -740px; }
-.emoji-1F6C8 { background-position: 0 -760px; }
-.emoji-1F6C9 { background-position: -20px -760px; }
-.emoji-1F6CA { background-position: -40px -760px; }
-.emoji-1F6CB { background-position: -60px -760px; }
-.emoji-1F6CC { background-position: -80px -760px; }
-.emoji-1F6CD { background-position: -100px -760px; }
-.emoji-1F6CE { background-position: -120px -760px; }
-.emoji-1F6CF { background-position: -140px -760px; }
-.emoji-1F6D0 { background-position: -160px -760px; }
-.emoji-1F6E0 { background-position: -180px -760px; }
-.emoji-1F6E1 { background-position: -200px -760px; }
-.emoji-1F6E2 { background-position: -220px -760px; }
-.emoji-1F6E3 { background-position: -240px -760px; }
-.emoji-1F6E4 { background-position: -260px -760px; }
-.emoji-1F6E5 { background-position: -280px -760px; }
-.emoji-1F6E6 { background-position: -300px -760px; }
-.emoji-1F6E7 { background-position: -320px -760px; }
-.emoji-1F6E8 { background-position: -340px -760px; }
-.emoji-1F6E9 { background-position: -360px -760px; }
-.emoji-1F6EA { background-position: -380px -760px; }
-.emoji-1F6EB { background-position: -400px -760px; }
-.emoji-1F6EC { background-position: -420px -760px; }
-.emoji-1F6F0 { background-position: -440px -760px; }
-.emoji-1F6F1 { background-position: -460px -760px; }
-.emoji-1F6F2 { background-position: -480px -760px; }
-.emoji-1F6F3 { background-position: -500px -760px; }
-.emoji-1F910 { background-position: -520px -760px; }
-.emoji-1F911 { background-position: -540px -760px; }
-.emoji-1F912 { background-position: -560px -760px; }
-.emoji-1F913 { background-position: -580px -760px; }
-.emoji-1F914 { background-position: -600px -760px; }
-.emoji-1F915 { background-position: -620px -760px; }
-.emoji-1F916 { background-position: -640px -760px; }
-.emoji-1F917 { background-position: -660px -760px; }
-.emoji-1F918 { background-position: -680px -760px; }
-.emoji-1F918-1F3FB { background-position: -700px -760px; }
-.emoji-1F918-1F3FC { background-position: -720px -760px; }
-.emoji-1F918-1F3FD { background-position: -740px -760px; }
-.emoji-1F918-1F3FE { background-position: -760px -760px; }
-.emoji-1F918-1F3FF { background-position: -780px 0; }
-.emoji-1F980 { background-position: -780px -20px; }
-.emoji-1F981 { background-position: -780px -40px; }
-.emoji-1F982 { background-position: -780px -60px; }
-.emoji-1F983 { background-position: -780px -80px; }
-.emoji-1F984 { background-position: -780px -100px; }
-.emoji-1F9C0 { background-position: -780px -120px; }
-.emoji-203C { background-position: -780px -140px; }
-.emoji-2049 { background-position: -780px -160px; }
-.emoji-2122 { background-position: -780px -180px; }
-.emoji-2139 { background-position: -780px -200px; }
-.emoji-2194 { background-position: -780px -220px; }
-.emoji-2195 { background-position: -780px -240px; }
-.emoji-2196 { background-position: -780px -260px; }
-.emoji-2197 { background-position: -780px -280px; }
-.emoji-2198 { background-position: -780px -300px; }
-.emoji-2199 { background-position: -780px -320px; }
-.emoji-21A9 { background-position: -780px -340px; }
-.emoji-21AA { background-position: -780px -360px; }
-.emoji-231A { background-position: -780px -380px; }
-.emoji-231B { background-position: -780px -400px; }
-.emoji-2328 { background-position: -780px -420px; }
-.emoji-23E9 { background-position: -780px -440px; }
-.emoji-23EA { background-position: -780px -460px; }
-.emoji-23EB { background-position: -780px -480px; }
-.emoji-23EC { background-position: -780px -500px; }
-.emoji-23ED { background-position: -780px -520px; }
-.emoji-23EE { background-position: -780px -540px; }
-.emoji-23EF { background-position: -780px -560px; }
-.emoji-23F0 { background-position: -780px -580px; }
-.emoji-23F1 { background-position: -780px -600px; }
-.emoji-23F2 { background-position: -780px -620px; }
-.emoji-23F3 { background-position: -780px -640px; }
-.emoji-23F8 { background-position: -780px -660px; }
-.emoji-23F9 { background-position: -780px -680px; }
-.emoji-23FA { background-position: -780px -700px; }
-.emoji-24C2 { background-position: -780px -720px; }
-.emoji-25AA { background-position: -780px -740px; }
-.emoji-25AB { background-position: -780px -760px; }
-.emoji-25B6 { background-position: 0 -780px; }
-.emoji-25C0 { background-position: -20px -780px; }
-.emoji-25FB { background-position: -40px -780px; }
-.emoji-25FC { background-position: -60px -780px; }
-.emoji-25FD { background-position: -80px -780px; }
-.emoji-25FE { background-position: -100px -780px; }
-.emoji-2600 { background-position: -120px -780px; }
-.emoji-2601 { background-position: -140px -780px; }
-.emoji-2602 { background-position: -160px -780px; }
-.emoji-2603 { background-position: -180px -780px; }
-.emoji-2604 { background-position: -200px -780px; }
-.emoji-260E { background-position: -220px -780px; }
-.emoji-2611 { background-position: -240px -780px; }
-.emoji-2614 { background-position: -260px -780px; }
-.emoji-2615 { background-position: -280px -780px; }
-.emoji-2618 { background-position: -300px -780px; }
-.emoji-261D { background-position: -320px -780px; }
-.emoji-261D-1F3FB { background-position: -340px -780px; }
-.emoji-261D-1F3FC { background-position: -360px -780px; }
-.emoji-261D-1F3FD { background-position: -380px -780px; }
-.emoji-261D-1F3FE { background-position: -400px -780px; }
-.emoji-261D-1F3FF { background-position: -420px -780px; }
-.emoji-2620 { background-position: -440px -780px; }
-.emoji-2622 { background-position: -460px -780px; }
-.emoji-2623 { background-position: -480px -780px; }
-.emoji-2626 { background-position: -500px -780px; }
-.emoji-262A { background-position: -520px -780px; }
-.emoji-262E { background-position: -540px -780px; }
-.emoji-262F { background-position: -560px -780px; }
-.emoji-2638 { background-position: -580px -780px; }
-.emoji-2639 { background-position: -600px -780px; }
-.emoji-263A { background-position: -620px -780px; }
-.emoji-2648 { background-position: -640px -780px; }
-.emoji-2649 { background-position: -660px -780px; }
-.emoji-264A { background-position: -680px -780px; }
-.emoji-264B { background-position: -700px -780px; }
-.emoji-264C { background-position: -720px -780px; }
-.emoji-264D { background-position: -740px -780px; }
-.emoji-264E { background-position: -760px -780px; }
-.emoji-264F { background-position: -780px -780px; }
-.emoji-2650 { background-position: -800px 0; }
-.emoji-2651 { background-position: -800px -20px; }
-.emoji-2652 { background-position: -800px -40px; }
-.emoji-2653 { background-position: -800px -60px; }
-.emoji-2660 { background-position: -800px -80px; }
-.emoji-2663 { background-position: -800px -100px; }
-.emoji-2665 { background-position: -800px -120px; }
-.emoji-2666 { background-position: -800px -140px; }
-.emoji-2668 { background-position: -800px -160px; }
-.emoji-267B { background-position: -800px -180px; }
-.emoji-267F { background-position: -800px -200px; }
-.emoji-2692 { background-position: -800px -220px; }
-.emoji-2693 { background-position: -800px -240px; }
-.emoji-2694 { background-position: -800px -260px; }
-.emoji-2696 { background-position: -800px -280px; }
-.emoji-2697 { background-position: -800px -300px; }
-.emoji-2699 { background-position: -800px -320px; }
-.emoji-269B { background-position: -800px -340px; }
-.emoji-269C { background-position: -800px -360px; }
-.emoji-26A0 { background-position: -800px -380px; }
-.emoji-26A1 { background-position: -800px -400px; }
-.emoji-26AA { background-position: -800px -420px; }
-.emoji-26AB { background-position: -800px -440px; }
-.emoji-26B0 { background-position: -800px -460px; }
-.emoji-26B1 { background-position: -800px -480px; }
-.emoji-26BD { background-position: -800px -500px; }
-.emoji-26BE { background-position: -800px -520px; }
-.emoji-26C4 { background-position: -800px -540px; }
-.emoji-26C5 { background-position: -800px -560px; }
-.emoji-26C8 { background-position: -800px -580px; }
-.emoji-26CE { background-position: -800px -600px; }
-.emoji-26CF { background-position: -800px -620px; }
-.emoji-26D1 { background-position: -800px -640px; }
-.emoji-26D3 { background-position: -800px -660px; }
-.emoji-26D4 { background-position: -800px -680px; }
-.emoji-26E9 { background-position: -800px -700px; }
-.emoji-26EA { background-position: -800px -720px; }
-.emoji-26F0 { background-position: -800px -740px; }
-.emoji-26F1 { background-position: -800px -760px; }
-.emoji-26F2 { background-position: -800px -780px; }
-.emoji-26F3 { background-position: 0 -800px; }
-.emoji-26F4 { background-position: -20px -800px; }
-.emoji-26F5 { background-position: -40px -800px; }
-.emoji-26F7 { background-position: -60px -800px; }
-.emoji-26F8 { background-position: -80px -800px; }
-.emoji-26F9 { background-position: -100px -800px; }
-.emoji-26F9-1F3FB { background-position: -120px -800px; }
-.emoji-26F9-1F3FC { background-position: -140px -800px; }
-.emoji-26F9-1F3FD { background-position: -160px -800px; }
-.emoji-26F9-1F3FE { background-position: -180px -800px; }
-.emoji-26F9-1F3FF { background-position: -200px -800px; }
-.emoji-26FA { background-position: -220px -800px; }
-.emoji-26FD { background-position: -240px -800px; }
-.emoji-2702 { background-position: -260px -800px; }
-.emoji-2705 { background-position: -280px -800px; }
-.emoji-2708 { background-position: -300px -800px; }
-.emoji-2709 { background-position: -320px -800px; }
-.emoji-270A { background-position: -340px -800px; }
-.emoji-270A-1F3FB { background-position: -360px -800px; }
-.emoji-270A-1F3FC { background-position: -380px -800px; }
-.emoji-270A-1F3FD { background-position: -400px -800px; }
-.emoji-270A-1F3FE { background-position: -420px -800px; }
-.emoji-270A-1F3FF { background-position: -440px -800px; }
-.emoji-270B { background-position: -460px -800px; }
-.emoji-270B-1F3FB { background-position: -480px -800px; }
-.emoji-270B-1F3FC { background-position: -500px -800px; }
-.emoji-270B-1F3FD { background-position: -520px -800px; }
-.emoji-270B-1F3FE { background-position: -540px -800px; }
-.emoji-270B-1F3FF { background-position: -560px -800px; }
-.emoji-270C { background-position: -580px -800px; }
-.emoji-270C-1F3FB { background-position: -600px -800px; }
-.emoji-270C-1F3FC { background-position: -620px -800px; }
-.emoji-270C-1F3FD { background-position: -640px -800px; }
-.emoji-270C-1F3FE { background-position: -660px -800px; }
-.emoji-270C-1F3FF { background-position: -680px -800px; }
-.emoji-270D { background-position: -700px -800px; }
-.emoji-270D-1F3FB { background-position: -720px -800px; }
-.emoji-270D-1F3FC { background-position: -740px -800px; }
-.emoji-270D-1F3FD { background-position: -760px -800px; }
-.emoji-270D-1F3FE { background-position: -780px -800px; }
-.emoji-270D-1F3FF { background-position: -800px -800px; }
-.emoji-270F { background-position: -820px 0; }
-.emoji-2712 { background-position: -820px -20px; }
-.emoji-2714 { background-position: -820px -40px; }
-.emoji-2716 { background-position: -820px -60px; }
-.emoji-271D { background-position: -820px -80px; }
-.emoji-2721 { background-position: -820px -100px; }
-.emoji-2728 { background-position: -820px -120px; }
-.emoji-2733 { background-position: -820px -140px; }
-.emoji-2734 { background-position: -820px -160px; }
-.emoji-2744 { background-position: -820px -180px; }
-.emoji-2747 { background-position: -820px -200px; }
-.emoji-274C { background-position: -820px -220px; }
-.emoji-274E { background-position: -820px -240px; }
-.emoji-2753 { background-position: -820px -260px; }
-.emoji-2754 { background-position: -820px -280px; }
-.emoji-2755 { background-position: -820px -300px; }
-.emoji-2757 { background-position: -820px -320px; }
-.emoji-2763 { background-position: -820px -340px; }
-.emoji-2764 { background-position: -820px -360px; }
-.emoji-2795 { background-position: -820px -380px; }
-.emoji-2796 { background-position: -820px -400px; }
-.emoji-2797 { background-position: -820px -420px; }
-.emoji-27A1 { background-position: -820px -440px; }
-.emoji-27B0 { background-position: -820px -460px; }
-.emoji-27BF { background-position: -820px -480px; }
-.emoji-2934 { background-position: -820px -500px; }
-.emoji-2935 { background-position: -820px -520px; }
-.emoji-2B05 { background-position: -820px -540px; }
-.emoji-2B06 { background-position: -820px -560px; }
-.emoji-2B07 { background-position: -820px -580px; }
-.emoji-2B1B { background-position: -820px -600px; }
-.emoji-2B1C { background-position: -820px -620px; }
-.emoji-2B50 { background-position: -820px -640px; }
-.emoji-2B55 { background-position: -820px -660px; }
-.emoji-3030 { background-position: -820px -680px; }
-.emoji-303D { background-position: -820px -700px; }
-.emoji-3297 { background-position: -820px -720px; }
-.emoji-3299 { background-position: -820px -740px; }
+.emoji-1F396 { background-position: -420px -260px; }
+.emoji-1F397 { background-position: -420px -280px; }
+.emoji-1F399 { background-position: -420px -300px; }
+.emoji-1F39A { background-position: -420px -320px; }
+.emoji-1F39B { background-position: -420px -340px; }
+.emoji-1F39E { background-position: -420px -360px; }
+.emoji-1F39F { background-position: -420px -380px; }
+.emoji-1F3A0 { background-position: -420px -400px; }
+.emoji-1F3A1 { background-position: 0 -420px; }
+.emoji-1F3A2 { background-position: -20px -420px; }
+.emoji-1F3A3 { background-position: -40px -420px; }
+.emoji-1F3A4 { background-position: -60px -420px; }
+.emoji-1F3A5 { background-position: -80px -420px; }
+.emoji-1F3A6 { background-position: -100px -420px; }
+.emoji-1F3A7 { background-position: -120px -420px; }
+.emoji-1F3A8 { background-position: -140px -420px; }
+.emoji-1F3A9 { background-position: -160px -420px; }
+.emoji-1F3AA { background-position: -180px -420px; }
+.emoji-1F3AB { background-position: -200px -420px; }
+.emoji-1F3AC { background-position: -220px -420px; }
+.emoji-1F3AD { background-position: -240px -420px; }
+.emoji-1F3AE { background-position: -260px -420px; }
+.emoji-1F3AF { background-position: -280px -420px; }
+.emoji-1F3B0 { background-position: -300px -420px; }
+.emoji-1F3B1 { background-position: -320px -420px; }
+.emoji-1F3B2 { background-position: -340px -420px; }
+.emoji-1F3B3 { background-position: -360px -420px; }
+.emoji-1F3B4 { background-position: -380px -420px; }
+.emoji-1F3B5 { background-position: -400px -420px; }
+.emoji-1F3B6 { background-position: -420px -420px; }
+.emoji-1F3B7 { background-position: -440px 0; }
+.emoji-1F3B8 { background-position: -440px -20px; }
+.emoji-1F3B9 { background-position: -440px -40px; }
+.emoji-1F3BA { background-position: -440px -60px; }
+.emoji-1F3BB { background-position: -440px -80px; }
+.emoji-1F3BC { background-position: -440px -100px; }
+.emoji-1F3BD { background-position: -440px -120px; }
+.emoji-1F3BE { background-position: -440px -140px; }
+.emoji-1F3BF { background-position: -440px -160px; }
+.emoji-1F3C0 { background-position: -440px -180px; }
+.emoji-1F3C1 { background-position: -440px -200px; }
+.emoji-1F3C2 { background-position: -440px -220px; }
+.emoji-1F3C3 { background-position: -440px -240px; }
+.emoji-1F3C3-1F3FB { background-position: -440px -260px; }
+.emoji-1F3C3-1F3FC { background-position: -440px -280px; }
+.emoji-1F3C3-1F3FD { background-position: -440px -300px; }
+.emoji-1F3C3-1F3FE { background-position: -440px -320px; }
+.emoji-1F3C3-1F3FF { background-position: -440px -340px; }
+.emoji-1F3C4 { background-position: -440px -360px; }
+.emoji-1F3C4-1F3FB { background-position: -440px -380px; }
+.emoji-1F3C4-1F3FC { background-position: -440px -400px; }
+.emoji-1F3C4-1F3FD { background-position: -440px -420px; }
+.emoji-1F3C4-1F3FE { background-position: 0 -440px; }
+.emoji-1F3C4-1F3FF { background-position: -20px -440px; }
+.emoji-1F3C5 { background-position: -40px -440px; }
+.emoji-1F3C6 { background-position: -60px -440px; }
+.emoji-1F3C7 { background-position: -80px -440px; }
+.emoji-1F3C7-1F3FB { background-position: -100px -440px; }
+.emoji-1F3C7-1F3FC { background-position: -120px -440px; }
+.emoji-1F3C7-1F3FD { background-position: -140px -440px; }
+.emoji-1F3C7-1F3FE { background-position: -160px -440px; }
+.emoji-1F3C7-1F3FF { background-position: -180px -440px; }
+.emoji-1F3C8 { background-position: -200px -440px; }
+.emoji-1F3C9 { background-position: -220px -440px; }
+.emoji-1F3CA { background-position: -240px -440px; }
+.emoji-1F3CA-1F3FB { background-position: -260px -440px; }
+.emoji-1F3CA-1F3FC { background-position: -280px -440px; }
+.emoji-1F3CA-1F3FD { background-position: -300px -440px; }
+.emoji-1F3CA-1F3FE { background-position: -320px -440px; }
+.emoji-1F3CA-1F3FF { background-position: -340px -440px; }
+.emoji-1F3CB { background-position: -360px -440px; }
+.emoji-1F3CB-1F3FB { background-position: -380px -440px; }
+.emoji-1F3CB-1F3FC { background-position: -400px -440px; }
+.emoji-1F3CB-1F3FD { background-position: -420px -440px; }
+.emoji-1F3CB-1F3FE { background-position: -440px -440px; }
+.emoji-1F3CB-1F3FF { background-position: -460px 0; }
+.emoji-1F3CC { background-position: -460px -20px; }
+.emoji-1F3CD { background-position: -460px -40px; }
+.emoji-1F3CE { background-position: -460px -60px; }
+.emoji-1F3CF { background-position: -460px -80px; }
+.emoji-1F3D0 { background-position: -460px -100px; }
+.emoji-1F3D1 { background-position: -460px -120px; }
+.emoji-1F3D2 { background-position: -460px -140px; }
+.emoji-1F3D3 { background-position: -460px -160px; }
+.emoji-1F3D4 { background-position: -460px -180px; }
+.emoji-1F3D5 { background-position: -460px -200px; }
+.emoji-1F3D6 { background-position: -460px -220px; }
+.emoji-1F3D7 { background-position: -460px -240px; }
+.emoji-1F3D8 { background-position: -460px -260px; }
+.emoji-1F3D9 { background-position: -460px -280px; }
+.emoji-1F3DA { background-position: -460px -300px; }
+.emoji-1F3DB { background-position: -460px -320px; }
+.emoji-1F3DC { background-position: -460px -340px; }
+.emoji-1F3DD { background-position: -460px -360px; }
+.emoji-1F3DE { background-position: -460px -380px; }
+.emoji-1F3DF { background-position: -460px -400px; }
+.emoji-1F3E0 { background-position: -460px -420px; }
+.emoji-1F3E1 { background-position: -460px -440px; }
+.emoji-1F3E2 { background-position: 0 -460px; }
+.emoji-1F3E3 { background-position: -20px -460px; }
+.emoji-1F3E4 { background-position: -40px -460px; }
+.emoji-1F3E5 { background-position: -60px -460px; }
+.emoji-1F3E6 { background-position: -80px -460px; }
+.emoji-1F3E7 { background-position: -100px -460px; }
+.emoji-1F3E8 { background-position: -120px -460px; }
+.emoji-1F3E9 { background-position: -140px -460px; }
+.emoji-1F3EA { background-position: -160px -460px; }
+.emoji-1F3EB { background-position: -180px -460px; }
+.emoji-1F3EC { background-position: -200px -460px; }
+.emoji-1F3ED { background-position: -220px -460px; }
+.emoji-1F3EE { background-position: -240px -460px; }
+.emoji-1F3EF { background-position: -260px -460px; }
+.emoji-1F3F0 { background-position: -280px -460px; }
+.emoji-1F3F3 { background-position: -300px -460px; }
+.emoji-1F3F4 { background-position: -320px -460px; }
+.emoji-1F3F5 { background-position: -340px -460px; }
+.emoji-1F3F7 { background-position: -360px -460px; }
+.emoji-1F3F8 { background-position: -380px -460px; }
+.emoji-1F3F9 { background-position: -400px -460px; }
+.emoji-1F3FA { background-position: -420px -460px; }
+.emoji-1F3FB { background-position: -440px -460px; }
+.emoji-1F3FC { background-position: -460px -460px; }
+.emoji-1F3FD { background-position: -480px 0; }
+.emoji-1F3FE { background-position: -480px -20px; }
+.emoji-1F3FF { background-position: -480px -40px; }
+.emoji-1F400 { background-position: -480px -60px; }
+.emoji-1F401 { background-position: -480px -80px; }
+.emoji-1F402 { background-position: -480px -100px; }
+.emoji-1F403 { background-position: -480px -120px; }
+.emoji-1F404 { background-position: -480px -140px; }
+.emoji-1F405 { background-position: -480px -160px; }
+.emoji-1F406 { background-position: -480px -180px; }
+.emoji-1F407 { background-position: -480px -200px; }
+.emoji-1F408 { background-position: -480px -220px; }
+.emoji-1F409 { background-position: -480px -240px; }
+.emoji-1F40A { background-position: -480px -260px; }
+.emoji-1F40B { background-position: -480px -280px; }
+.emoji-1F40C { background-position: -480px -300px; }
+.emoji-1F40D { background-position: -480px -320px; }
+.emoji-1F40E { background-position: -480px -340px; }
+.emoji-1F40F { background-position: -480px -360px; }
+.emoji-1F410 { background-position: -480px -380px; }
+.emoji-1F411 { background-position: -480px -400px; }
+.emoji-1F412 { background-position: -480px -420px; }
+.emoji-1F413 { background-position: -480px -440px; }
+.emoji-1F414 { background-position: -480px -460px; }
+.emoji-1F415 { background-position: 0 -480px; }
+.emoji-1F416 { background-position: -20px -480px; }
+.emoji-1F417 { background-position: -40px -480px; }
+.emoji-1F418 { background-position: -60px -480px; }
+.emoji-1F419 { background-position: -80px -480px; }
+.emoji-1F41A { background-position: -100px -480px; }
+.emoji-1F41B { background-position: -120px -480px; }
+.emoji-1F41C { background-position: -140px -480px; }
+.emoji-1F41D { background-position: -160px -480px; }
+.emoji-1F41E { background-position: -180px -480px; }
+.emoji-1F41F { background-position: -200px -480px; }
+.emoji-1F420 { background-position: -220px -480px; }
+.emoji-1F421 { background-position: -240px -480px; }
+.emoji-1F422 { background-position: -260px -480px; }
+.emoji-1F423 { background-position: -280px -480px; }
+.emoji-1F424 { background-position: -300px -480px; }
+.emoji-1F425 { background-position: -320px -480px; }
+.emoji-1F426 { background-position: -340px -480px; }
+.emoji-1F427 { background-position: -360px -480px; }
+.emoji-1F428 { background-position: -380px -480px; }
+.emoji-1F429 { background-position: -400px -480px; }
+.emoji-1F42A { background-position: -420px -480px; }
+.emoji-1F42B { background-position: -440px -480px; }
+.emoji-1F42C { background-position: -460px -480px; }
+.emoji-1F42D { background-position: -480px -480px; }
+.emoji-1F42E { background-position: -500px 0; }
+.emoji-1F42F { background-position: -500px -20px; }
+.emoji-1F430 { background-position: -500px -40px; }
+.emoji-1F431 { background-position: -500px -60px; }
+.emoji-1F432 { background-position: -500px -80px; }
+.emoji-1F433 { background-position: -500px -100px; }
+.emoji-1F434 { background-position: -500px -120px; }
+.emoji-1F435 { background-position: -500px -140px; }
+.emoji-1F436 { background-position: -500px -160px; }
+.emoji-1F437 { background-position: -500px -180px; }
+.emoji-1F438 { background-position: -500px -200px; }
+.emoji-1F439 { background-position: -500px -220px; }
+.emoji-1F43A { background-position: -500px -240px; }
+.emoji-1F43B { background-position: -500px -260px; }
+.emoji-1F43C { background-position: -500px -280px; }
+.emoji-1F43D { background-position: -500px -300px; }
+.emoji-1F43E { background-position: -500px -320px; }
+.emoji-1F43F { background-position: -500px -340px; }
+.emoji-1F440 { background-position: -500px -360px; }
+.emoji-1F441 { background-position: -500px -380px; }
+.emoji-1F441-1F5E8 { background-position: -500px -400px; }
+.emoji-1F442 { background-position: -500px -420px; }
+.emoji-1F442-1F3FB { background-position: -500px -440px; }
+.emoji-1F442-1F3FC { background-position: -500px -460px; }
+.emoji-1F442-1F3FD { background-position: -500px -480px; }
+.emoji-1F442-1F3FE { background-position: 0 -500px; }
+.emoji-1F442-1F3FF { background-position: -20px -500px; }
+.emoji-1F443 { background-position: -40px -500px; }
+.emoji-1F443-1F3FB { background-position: -60px -500px; }
+.emoji-1F443-1F3FC { background-position: -80px -500px; }
+.emoji-1F443-1F3FD { background-position: -100px -500px; }
+.emoji-1F443-1F3FE { background-position: -120px -500px; }
+.emoji-1F443-1F3FF { background-position: -140px -500px; }
+.emoji-1F444 { background-position: -160px -500px; }
+.emoji-1F445 { background-position: -180px -500px; }
+.emoji-1F446 { background-position: -200px -500px; }
+.emoji-1F446-1F3FB { background-position: -220px -500px; }
+.emoji-1F446-1F3FC { background-position: -240px -500px; }
+.emoji-1F446-1F3FD { background-position: -260px -500px; }
+.emoji-1F446-1F3FE { background-position: -280px -500px; }
+.emoji-1F446-1F3FF { background-position: -300px -500px; }
+.emoji-1F447 { background-position: -320px -500px; }
+.emoji-1F447-1F3FB { background-position: -340px -500px; }
+.emoji-1F447-1F3FC { background-position: -360px -500px; }
+.emoji-1F447-1F3FD { background-position: -380px -500px; }
+.emoji-1F447-1F3FE { background-position: -400px -500px; }
+.emoji-1F447-1F3FF { background-position: -420px -500px; }
+.emoji-1F448 { background-position: -440px -500px; }
+.emoji-1F448-1F3FB { background-position: -460px -500px; }
+.emoji-1F448-1F3FC { background-position: -480px -500px; }
+.emoji-1F448-1F3FD { background-position: -500px -500px; }
+.emoji-1F448-1F3FE { background-position: -520px 0; }
+.emoji-1F448-1F3FF { background-position: -520px -20px; }
+.emoji-1F449 { background-position: -520px -40px; }
+.emoji-1F449-1F3FB { background-position: -520px -60px; }
+.emoji-1F449-1F3FC { background-position: -520px -80px; }
+.emoji-1F449-1F3FD { background-position: -520px -100px; }
+.emoji-1F449-1F3FE { background-position: -520px -120px; }
+.emoji-1F449-1F3FF { background-position: -520px -140px; }
+.emoji-1F44A { background-position: -520px -160px; }
+.emoji-1F44A-1F3FB { background-position: -520px -180px; }
+.emoji-1F44A-1F3FC { background-position: -520px -200px; }
+.emoji-1F44A-1F3FD { background-position: -520px -220px; }
+.emoji-1F44A-1F3FE { background-position: -520px -240px; }
+.emoji-1F44A-1F3FF { background-position: -520px -260px; }
+.emoji-1F44B { background-position: -520px -280px; }
+.emoji-1F44B-1F3FB { background-position: -520px -300px; }
+.emoji-1F44B-1F3FC { background-position: -520px -320px; }
+.emoji-1F44B-1F3FD { background-position: -520px -340px; }
+.emoji-1F44B-1F3FE { background-position: -520px -360px; }
+.emoji-1F44B-1F3FF { background-position: -520px -380px; }
+.emoji-1F44C { background-position: -520px -400px; }
+.emoji-1F44C-1F3FB { background-position: -520px -420px; }
+.emoji-1F44C-1F3FC { background-position: -520px -440px; }
+.emoji-1F44C-1F3FD { background-position: -520px -460px; }
+.emoji-1F44C-1F3FE { background-position: -520px -480px; }
+.emoji-1F44C-1F3FF { background-position: -520px -500px; }
+.emoji-1F44D { background-position: 0 -520px; }
+.emoji-1F44D-1F3FB { background-position: -20px -520px; }
+.emoji-1F44D-1F3FC { background-position: -40px -520px; }
+.emoji-1F44D-1F3FD { background-position: -60px -520px; }
+.emoji-1F44D-1F3FE { background-position: -80px -520px; }
+.emoji-1F44D-1F3FF { background-position: -100px -520px; }
+.emoji-1F44E { background-position: -120px -520px; }
+.emoji-1F44E-1F3FB { background-position: -140px -520px; }
+.emoji-1F44E-1F3FC { background-position: -160px -520px; }
+.emoji-1F44E-1F3FD { background-position: -180px -520px; }
+.emoji-1F44E-1F3FE { background-position: -200px -520px; }
+.emoji-1F44E-1F3FF { background-position: -220px -520px; }
+.emoji-1F44F { background-position: -240px -520px; }
+.emoji-1F44F-1F3FB { background-position: -260px -520px; }
+.emoji-1F44F-1F3FC { background-position: -280px -520px; }
+.emoji-1F44F-1F3FD { background-position: -300px -520px; }
+.emoji-1F44F-1F3FE { background-position: -320px -520px; }
+.emoji-1F44F-1F3FF { background-position: -340px -520px; }
+.emoji-1F450 { background-position: -360px -520px; }
+.emoji-1F450-1F3FB { background-position: -380px -520px; }
+.emoji-1F450-1F3FC { background-position: -400px -520px; }
+.emoji-1F450-1F3FD { background-position: -420px -520px; }
+.emoji-1F450-1F3FE { background-position: -440px -520px; }
+.emoji-1F450-1F3FF { background-position: -460px -520px; }
+.emoji-1F451 { background-position: -480px -520px; }
+.emoji-1F452 { background-position: -500px -520px; }
+.emoji-1F453 { background-position: -520px -520px; }
+.emoji-1F454 { background-position: -540px 0; }
+.emoji-1F455 { background-position: -540px -20px; }
+.emoji-1F456 { background-position: -540px -40px; }
+.emoji-1F457 { background-position: -540px -60px; }
+.emoji-1F458 { background-position: -540px -80px; }
+.emoji-1F459 { background-position: -540px -100px; }
+.emoji-1F45A { background-position: -540px -120px; }
+.emoji-1F45B { background-position: -540px -140px; }
+.emoji-1F45C { background-position: -540px -160px; }
+.emoji-1F45D { background-position: -540px -180px; }
+.emoji-1F45E { background-position: -540px -200px; }
+.emoji-1F45F { background-position: -540px -220px; }
+.emoji-1F460 { background-position: -540px -240px; }
+.emoji-1F461 { background-position: -540px -260px; }
+.emoji-1F462 { background-position: -540px -280px; }
+.emoji-1F463 { background-position: -540px -300px; }
+.emoji-1F464 { background-position: -540px -320px; }
+.emoji-1F465 { background-position: -540px -340px; }
+.emoji-1F466 { background-position: -540px -360px; }
+.emoji-1F466-1F3FB { background-position: -540px -380px; }
+.emoji-1F466-1F3FC { background-position: -540px -400px; }
+.emoji-1F466-1F3FD { background-position: -540px -420px; }
+.emoji-1F466-1F3FE { background-position: -540px -440px; }
+.emoji-1F466-1F3FF { background-position: -540px -460px; }
+.emoji-1F467 { background-position: -540px -480px; }
+.emoji-1F467-1F3FB { background-position: -540px -500px; }
+.emoji-1F467-1F3FC { background-position: -540px -520px; }
+.emoji-1F467-1F3FD { background-position: 0 -540px; }
+.emoji-1F467-1F3FE { background-position: -20px -540px; }
+.emoji-1F467-1F3FF { background-position: -40px -540px; }
+.emoji-1F468 { background-position: -60px -540px; }
+.emoji-1F468-1F3FB { background-position: -80px -540px; }
+.emoji-1F468-1F3FC { background-position: -100px -540px; }
+.emoji-1F468-1F3FD { background-position: -120px -540px; }
+.emoji-1F468-1F3FE { background-position: -140px -540px; }
+.emoji-1F468-1F3FF { background-position: -160px -540px; }
+.emoji-1F468-1F468-1F466 { background-position: -180px -540px; }
+.emoji-1F468-1F468-1F466-1F466 { background-position: -200px -540px; }
+.emoji-1F468-1F468-1F467 { background-position: -220px -540px; }
+.emoji-1F468-1F468-1F467-1F466 { background-position: -240px -540px; }
+.emoji-1F468-1F468-1F467-1F467 { background-position: -260px -540px; }
+.emoji-1F468-1F469-1F466-1F466 { background-position: -280px -540px; }
+.emoji-1F468-1F469-1F467 { background-position: -300px -540px; }
+.emoji-1F468-1F469-1F467-1F466 { background-position: -320px -540px; }
+.emoji-1F468-1F469-1F467-1F467 { background-position: -340px -540px; }
+.emoji-1F468-2764-1F468 { background-position: -360px -540px; }
+.emoji-1F468-2764-1F48B-1F468 { background-position: -380px -540px; }
+.emoji-1F469 { background-position: -400px -540px; }
+.emoji-1F469-1F3FB { background-position: -420px -540px; }
+.emoji-1F469-1F3FC { background-position: -440px -540px; }
+.emoji-1F469-1F3FD { background-position: -460px -540px; }
+.emoji-1F469-1F3FE { background-position: -480px -540px; }
+.emoji-1F469-1F3FF { background-position: -500px -540px; }
+.emoji-1F469-1F469-1F466 { background-position: -520px -540px; }
+.emoji-1F469-1F469-1F466-1F466 { background-position: -540px -540px; }
+.emoji-1F469-1F469-1F467 { background-position: -560px 0; }
+.emoji-1F469-1F469-1F467-1F466 { background-position: -560px -20px; }
+.emoji-1F469-1F469-1F467-1F467 { background-position: -560px -40px; }
+.emoji-1F469-2764-1F469 { background-position: -560px -60px; }
+.emoji-1F469-2764-1F48B-1F469 { background-position: -560px -80px; }
+.emoji-1F46A { background-position: -560px -100px; }
+.emoji-1F46B { background-position: -560px -120px; }
+.emoji-1F46C { background-position: -560px -140px; }
+.emoji-1F46D { background-position: -560px -160px; }
+.emoji-1F46E { background-position: -560px -180px; }
+.emoji-1F46E-1F3FB { background-position: -560px -200px; }
+.emoji-1F46E-1F3FC { background-position: -560px -220px; }
+.emoji-1F46E-1F3FD { background-position: -560px -240px; }
+.emoji-1F46E-1F3FE { background-position: -560px -260px; }
+.emoji-1F46E-1F3FF { background-position: -560px -280px; }
+.emoji-1F46F { background-position: -560px -300px; }
+.emoji-1F470 { background-position: -560px -320px; }
+.emoji-1F470-1F3FB { background-position: -560px -340px; }
+.emoji-1F470-1F3FC { background-position: -560px -360px; }
+.emoji-1F470-1F3FD { background-position: -560px -380px; }
+.emoji-1F470-1F3FE { background-position: -560px -400px; }
+.emoji-1F470-1F3FF { background-position: -560px -420px; }
+.emoji-1F471 { background-position: -560px -440px; }
+.emoji-1F471-1F3FB { background-position: -560px -460px; }
+.emoji-1F471-1F3FC { background-position: -560px -480px; }
+.emoji-1F471-1F3FD { background-position: -560px -500px; }
+.emoji-1F471-1F3FE { background-position: -560px -520px; }
+.emoji-1F471-1F3FF { background-position: -560px -540px; }
+.emoji-1F472 { background-position: 0 -560px; }
+.emoji-1F472-1F3FB { background-position: -20px -560px; }
+.emoji-1F472-1F3FC { background-position: -40px -560px; }
+.emoji-1F472-1F3FD { background-position: -60px -560px; }
+.emoji-1F472-1F3FE { background-position: -80px -560px; }
+.emoji-1F472-1F3FF { background-position: -100px -560px; }
+.emoji-1F473 { background-position: -120px -560px; }
+.emoji-1F473-1F3FB { background-position: -140px -560px; }
+.emoji-1F473-1F3FC { background-position: -160px -560px; }
+.emoji-1F473-1F3FD { background-position: -180px -560px; }
+.emoji-1F473-1F3FE { background-position: -200px -560px; }
+.emoji-1F473-1F3FF { background-position: -220px -560px; }
+.emoji-1F474 { background-position: -240px -560px; }
+.emoji-1F474-1F3FB { background-position: -260px -560px; }
+.emoji-1F474-1F3FC { background-position: -280px -560px; }
+.emoji-1F474-1F3FD { background-position: -300px -560px; }
+.emoji-1F474-1F3FE { background-position: -320px -560px; }
+.emoji-1F474-1F3FF { background-position: -340px -560px; }
+.emoji-1F475 { background-position: -360px -560px; }
+.emoji-1F475-1F3FB { background-position: -380px -560px; }
+.emoji-1F475-1F3FC { background-position: -400px -560px; }
+.emoji-1F475-1F3FD { background-position: -420px -560px; }
+.emoji-1F475-1F3FE { background-position: -440px -560px; }
+.emoji-1F475-1F3FF { background-position: -460px -560px; }
+.emoji-1F476 { background-position: -480px -560px; }
+.emoji-1F476-1F3FB { background-position: -500px -560px; }
+.emoji-1F476-1F3FC { background-position: -520px -560px; }
+.emoji-1F476-1F3FD { background-position: -540px -560px; }
+.emoji-1F476-1F3FE { background-position: -560px -560px; }
+.emoji-1F476-1F3FF { background-position: -580px 0; }
+.emoji-1F477 { background-position: -580px -20px; }
+.emoji-1F477-1F3FB { background-position: -580px -40px; }
+.emoji-1F477-1F3FC { background-position: -580px -60px; }
+.emoji-1F477-1F3FD { background-position: -580px -80px; }
+.emoji-1F477-1F3FE { background-position: -580px -100px; }
+.emoji-1F477-1F3FF { background-position: -580px -120px; }
+.emoji-1F478 { background-position: -580px -140px; }
+.emoji-1F478-1F3FB { background-position: -580px -160px; }
+.emoji-1F478-1F3FC { background-position: -580px -180px; }
+.emoji-1F478-1F3FD { background-position: -580px -200px; }
+.emoji-1F478-1F3FE { background-position: -580px -220px; }
+.emoji-1F478-1F3FF { background-position: -580px -240px; }
+.emoji-1F479 { background-position: -580px -260px; }
+.emoji-1F47A { background-position: -580px -280px; }
+.emoji-1F47B { background-position: -580px -300px; }
+.emoji-1F47C { background-position: -580px -320px; }
+.emoji-1F47C-1F3FB { background-position: -580px -340px; }
+.emoji-1F47C-1F3FC { background-position: -580px -360px; }
+.emoji-1F47C-1F3FD { background-position: -580px -380px; }
+.emoji-1F47C-1F3FE { background-position: -580px -400px; }
+.emoji-1F47C-1F3FF { background-position: -580px -420px; }
+.emoji-1F47D { background-position: -580px -440px; }
+.emoji-1F47E { background-position: -580px -460px; }
+.emoji-1F47F { background-position: -580px -480px; }
+.emoji-1F480 { background-position: -580px -500px; }
+.emoji-1F481 { background-position: -580px -520px; }
+.emoji-1F481-1F3FB { background-position: -580px -540px; }
+.emoji-1F481-1F3FC { background-position: -580px -560px; }
+.emoji-1F481-1F3FD { background-position: 0 -580px; }
+.emoji-1F481-1F3FE { background-position: -20px -580px; }
+.emoji-1F481-1F3FF { background-position: -40px -580px; }
+.emoji-1F482 { background-position: -60px -580px; }
+.emoji-1F482-1F3FB { background-position: -80px -580px; }
+.emoji-1F482-1F3FC { background-position: -100px -580px; }
+.emoji-1F482-1F3FD { background-position: -120px -580px; }
+.emoji-1F482-1F3FE { background-position: -140px -580px; }
+.emoji-1F482-1F3FF { background-position: -160px -580px; }
+.emoji-1F483 { background-position: -180px -580px; }
+.emoji-1F483-1F3FB { background-position: -200px -580px; }
+.emoji-1F483-1F3FC { background-position: -220px -580px; }
+.emoji-1F483-1F3FD { background-position: -240px -580px; }
+.emoji-1F483-1F3FE { background-position: -260px -580px; }
+.emoji-1F483-1F3FF { background-position: -280px -580px; }
+.emoji-1F484 { background-position: -300px -580px; }
+.emoji-1F485 { background-position: -320px -580px; }
+.emoji-1F485-1F3FB { background-position: -340px -580px; }
+.emoji-1F485-1F3FC { background-position: -360px -580px; }
+.emoji-1F485-1F3FD { background-position: -380px -580px; }
+.emoji-1F485-1F3FE { background-position: -400px -580px; }
+.emoji-1F485-1F3FF { background-position: -420px -580px; }
+.emoji-1F486 { background-position: -440px -580px; }
+.emoji-1F486-1F3FB { background-position: -460px -580px; }
+.emoji-1F486-1F3FC { background-position: -480px -580px; }
+.emoji-1F486-1F3FD { background-position: -500px -580px; }
+.emoji-1F486-1F3FE { background-position: -520px -580px; }
+.emoji-1F486-1F3FF { background-position: -540px -580px; }
+.emoji-1F487 { background-position: -560px -580px; }
+.emoji-1F487-1F3FB { background-position: -580px -580px; }
+.emoji-1F487-1F3FC { background-position: -600px 0; }
+.emoji-1F487-1F3FD { background-position: -600px -20px; }
+.emoji-1F487-1F3FE { background-position: -600px -40px; }
+.emoji-1F487-1F3FF { background-position: -600px -60px; }
+.emoji-1F488 { background-position: -600px -80px; }
+.emoji-1F489 { background-position: -600px -100px; }
+.emoji-1F48A { background-position: -600px -120px; }
+.emoji-1F48B { background-position: -600px -140px; }
+.emoji-1F48C { background-position: -600px -160px; }
+.emoji-1F48D { background-position: -600px -180px; }
+.emoji-1F48E { background-position: -600px -200px; }
+.emoji-1F48F { background-position: -600px -220px; }
+.emoji-1F490 { background-position: -600px -240px; }
+.emoji-1F491 { background-position: -600px -260px; }
+.emoji-1F492 { background-position: -600px -280px; }
+.emoji-1F493 { background-position: -600px -300px; }
+.emoji-1F494 { background-position: -600px -320px; }
+.emoji-1F495 { background-position: -600px -340px; }
+.emoji-1F496 { background-position: -600px -360px; }
+.emoji-1F497 { background-position: -600px -380px; }
+.emoji-1F498 { background-position: -600px -400px; }
+.emoji-1F499 { background-position: -600px -420px; }
+.emoji-1F49A { background-position: -600px -440px; }
+.emoji-1F49B { background-position: -600px -460px; }
+.emoji-1F49C { background-position: -600px -480px; }
+.emoji-1F49D { background-position: -600px -500px; }
+.emoji-1F49E { background-position: -600px -520px; }
+.emoji-1F49F { background-position: -600px -540px; }
+.emoji-1F4A0 { background-position: -600px -560px; }
+.emoji-1F4A1 { background-position: -600px -580px; }
+.emoji-1F4A2 { background-position: 0 -600px; }
+.emoji-1F4A3 { background-position: -20px -600px; }
+.emoji-1F4A4 { background-position: -40px -600px; }
+.emoji-1F4A5 { background-position: -60px -600px; }
+.emoji-1F4A6 { background-position: -80px -600px; }
+.emoji-1F4A7 { background-position: -100px -600px; }
+.emoji-1F4A8 { background-position: -120px -600px; }
+.emoji-1F4A9 { background-position: -140px -600px; }
+.emoji-1F4AA { background-position: -160px -600px; }
+.emoji-1F4AA-1F3FB { background-position: -180px -600px; }
+.emoji-1F4AA-1F3FC { background-position: -200px -600px; }
+.emoji-1F4AA-1F3FD { background-position: -220px -600px; }
+.emoji-1F4AA-1F3FE { background-position: -240px -600px; }
+.emoji-1F4AA-1F3FF { background-position: -260px -600px; }
+.emoji-1F4AB { background-position: -280px -600px; }
+.emoji-1F4AC { background-position: -300px -600px; }
+.emoji-1F4AD { background-position: -320px -600px; }
+.emoji-1F4AE { background-position: -340px -600px; }
+.emoji-1F4AF { background-position: -360px -600px; }
+.emoji-1F4B0 { background-position: -380px -600px; }
+.emoji-1F4B1 { background-position: -400px -600px; }
+.emoji-1F4B2 { background-position: -420px -600px; }
+.emoji-1F4B3 { background-position: -440px -600px; }
+.emoji-1F4B4 { background-position: -460px -600px; }
+.emoji-1F4B5 { background-position: -480px -600px; }
+.emoji-1F4B6 { background-position: -500px -600px; }
+.emoji-1F4B7 { background-position: -520px -600px; }
+.emoji-1F4B8 { background-position: -540px -600px; }
+.emoji-1F4B9 { background-position: -560px -600px; }
+.emoji-1F4BA { background-position: -580px -600px; }
+.emoji-1F4BB { background-position: -600px -600px; }
+.emoji-1F4BC { background-position: -620px 0; }
+.emoji-1F4BD { background-position: -620px -20px; }
+.emoji-1F4BE { background-position: -620px -40px; }
+.emoji-1F4BF { background-position: -620px -60px; }
+.emoji-1F4C0 { background-position: -620px -80px; }
+.emoji-1F4C1 { background-position: -620px -100px; }
+.emoji-1F4C2 { background-position: -620px -120px; }
+.emoji-1F4C3 { background-position: -620px -140px; }
+.emoji-1F4C4 { background-position: -620px -160px; }
+.emoji-1F4C5 { background-position: -620px -180px; }
+.emoji-1F4C6 { background-position: -620px -200px; }
+.emoji-1F4C7 { background-position: -620px -220px; }
+.emoji-1F4C8 { background-position: -620px -240px; }
+.emoji-1F4C9 { background-position: -620px -260px; }
+.emoji-1F4CA { background-position: -620px -280px; }
+.emoji-1F4CB { background-position: -620px -300px; }
+.emoji-1F4CC { background-position: -620px -320px; }
+.emoji-1F4CD { background-position: -620px -340px; }
+.emoji-1F4CE { background-position: -620px -360px; }
+.emoji-1F4CF { background-position: -620px -380px; }
+.emoji-1F4D0 { background-position: -620px -400px; }
+.emoji-1F4D1 { background-position: -620px -420px; }
+.emoji-1F4D2 { background-position: -620px -440px; }
+.emoji-1F4D3 { background-position: -620px -460px; }
+.emoji-1F4D4 { background-position: -620px -480px; }
+.emoji-1F4D5 { background-position: -620px -500px; }
+.emoji-1F4D6 { background-position: -620px -520px; }
+.emoji-1F4D7 { background-position: -620px -540px; }
+.emoji-1F4D8 { background-position: -620px -560px; }
+.emoji-1F4D9 { background-position: -620px -580px; }
+.emoji-1F4DA { background-position: -620px -600px; }
+.emoji-1F4DB { background-position: 0 -620px; }
+.emoji-1F4DC { background-position: -20px -620px; }
+.emoji-1F4DD { background-position: -40px -620px; }
+.emoji-1F4DE { background-position: -60px -620px; }
+.emoji-1F4DF { background-position: -80px -620px; }
+.emoji-1F4E0 { background-position: -100px -620px; }
+.emoji-1F4E1 { background-position: -120px -620px; }
+.emoji-1F4E2 { background-position: -140px -620px; }
+.emoji-1F4E3 { background-position: -160px -620px; }
+.emoji-1F4E4 { background-position: -180px -620px; }
+.emoji-1F4E5 { background-position: -200px -620px; }
+.emoji-1F4E6 { background-position: -220px -620px; }
+.emoji-1F4E7 { background-position: -240px -620px; }
+.emoji-1F4E8 { background-position: -260px -620px; }
+.emoji-1F4E9 { background-position: -280px -620px; }
+.emoji-1F4EA { background-position: -300px -620px; }
+.emoji-1F4EB { background-position: -320px -620px; }
+.emoji-1F4EC { background-position: -340px -620px; }
+.emoji-1F4ED { background-position: -360px -620px; }
+.emoji-1F4EE { background-position: -380px -620px; }
+.emoji-1F4EF { background-position: -400px -620px; }
+.emoji-1F4F0 { background-position: -420px -620px; }
+.emoji-1F4F1 { background-position: -440px -620px; }
+.emoji-1F4F2 { background-position: -460px -620px; }
+.emoji-1F4F3 { background-position: -480px -620px; }
+.emoji-1F4F4 { background-position: -500px -620px; }
+.emoji-1F4F5 { background-position: -520px -620px; }
+.emoji-1F4F6 { background-position: -540px -620px; }
+.emoji-1F4F7 { background-position: -560px -620px; }
+.emoji-1F4F8 { background-position: -580px -620px; }
+.emoji-1F4F9 { background-position: -600px -620px; }
+.emoji-1F4FA { background-position: -620px -620px; }
+.emoji-1F4FB { background-position: -640px 0; }
+.emoji-1F4FC { background-position: -640px -20px; }
+.emoji-1F4FD { background-position: -640px -40px; }
+.emoji-1F4FF { background-position: -640px -60px; }
+.emoji-1F500 { background-position: -640px -80px; }
+.emoji-1F501 { background-position: -640px -100px; }
+.emoji-1F502 { background-position: -640px -120px; }
+.emoji-1F503 { background-position: -640px -140px; }
+.emoji-1F504 { background-position: -640px -160px; }
+.emoji-1F505 { background-position: -640px -180px; }
+.emoji-1F506 { background-position: -640px -200px; }
+.emoji-1F507 { background-position: -640px -220px; }
+.emoji-1F508 { background-position: -640px -240px; }
+.emoji-1F509 { background-position: -640px -260px; }
+.emoji-1F50A { background-position: -640px -280px; }
+.emoji-1F50B { background-position: -640px -300px; }
+.emoji-1F50C { background-position: -640px -320px; }
+.emoji-1F50D { background-position: -640px -340px; }
+.emoji-1F50E { background-position: -640px -360px; }
+.emoji-1F50F { background-position: -640px -380px; }
+.emoji-1F510 { background-position: -640px -400px; }
+.emoji-1F511 { background-position: -640px -420px; }
+.emoji-1F512 { background-position: -640px -440px; }
+.emoji-1F513 { background-position: -640px -460px; }
+.emoji-1F514 { background-position: -640px -480px; }
+.emoji-1F515 { background-position: -640px -500px; }
+.emoji-1F516 { background-position: -640px -520px; }
+.emoji-1F517 { background-position: -640px -540px; }
+.emoji-1F518 { background-position: -640px -560px; }
+.emoji-1F519 { background-position: -640px -580px; }
+.emoji-1F51A { background-position: -640px -600px; }
+.emoji-1F51B { background-position: -640px -620px; }
+.emoji-1F51C { background-position: 0 -640px; }
+.emoji-1F51D { background-position: -20px -640px; }
+.emoji-1F51E { background-position: -40px -640px; }
+.emoji-1F51F { background-position: -60px -640px; }
+.emoji-1F520 { background-position: -80px -640px; }
+.emoji-1F521 { background-position: -100px -640px; }
+.emoji-1F522 { background-position: -120px -640px; }
+.emoji-1F523 { background-position: -140px -640px; }
+.emoji-1F524 { background-position: -160px -640px; }
+.emoji-1F525 { background-position: -180px -640px; }
+.emoji-1F526 { background-position: -200px -640px; }
+.emoji-1F527 { background-position: -220px -640px; }
+.emoji-1F528 { background-position: -240px -640px; }
+.emoji-1F529 { background-position: -260px -640px; }
+.emoji-1F52A { background-position: -280px -640px; }
+.emoji-1F52B { background-position: -300px -640px; }
+.emoji-1F52C { background-position: -320px -640px; }
+.emoji-1F52D { background-position: -340px -640px; }
+.emoji-1F52E { background-position: -360px -640px; }
+.emoji-1F52F { background-position: -380px -640px; }
+.emoji-1F530 { background-position: -400px -640px; }
+.emoji-1F531 { background-position: -420px -640px; }
+.emoji-1F532 { background-position: -440px -640px; }
+.emoji-1F533 { background-position: -460px -640px; }
+.emoji-1F534 { background-position: -480px -640px; }
+.emoji-1F535 { background-position: -500px -640px; }
+.emoji-1F536 { background-position: -520px -640px; }
+.emoji-1F537 { background-position: -540px -640px; }
+.emoji-1F538 { background-position: -560px -640px; }
+.emoji-1F539 { background-position: -580px -640px; }
+.emoji-1F53A { background-position: -600px -640px; }
+.emoji-1F53B { background-position: -620px -640px; }
+.emoji-1F53C { background-position: -640px -640px; }
+.emoji-1F53D { background-position: -660px 0; }
+.emoji-1F549 { background-position: -660px -20px; }
+.emoji-1F54A { background-position: -660px -40px; }
+.emoji-1F54B { background-position: -660px -60px; }
+.emoji-1F54C { background-position: -660px -80px; }
+.emoji-1F54D { background-position: -660px -100px; }
+.emoji-1F54E { background-position: -660px -120px; }
+.emoji-1F550 { background-position: -660px -140px; }
+.emoji-1F551 { background-position: -660px -160px; }
+.emoji-1F552 { background-position: -660px -180px; }
+.emoji-1F553 { background-position: -660px -200px; }
+.emoji-1F554 { background-position: -660px -220px; }
+.emoji-1F555 { background-position: -660px -240px; }
+.emoji-1F556 { background-position: -660px -260px; }
+.emoji-1F557 { background-position: -660px -280px; }
+.emoji-1F558 { background-position: -660px -300px; }
+.emoji-1F559 { background-position: -660px -320px; }
+.emoji-1F55A { background-position: -660px -340px; }
+.emoji-1F55B { background-position: -660px -360px; }
+.emoji-1F55C { background-position: -660px -380px; }
+.emoji-1F55D { background-position: -660px -400px; }
+.emoji-1F55E { background-position: -660px -420px; }
+.emoji-1F55F { background-position: -660px -440px; }
+.emoji-1F560 { background-position: -660px -460px; }
+.emoji-1F561 { background-position: -660px -480px; }
+.emoji-1F562 { background-position: -660px -500px; }
+.emoji-1F563 { background-position: -660px -520px; }
+.emoji-1F564 { background-position: -660px -540px; }
+.emoji-1F565 { background-position: -660px -560px; }
+.emoji-1F566 { background-position: -660px -580px; }
+.emoji-1F567 { background-position: -660px -600px; }
+.emoji-1F56F { background-position: -660px -620px; }
+.emoji-1F570 { background-position: -660px -640px; }
+.emoji-1F573 { background-position: 0 -660px; }
+.emoji-1F574 { background-position: -20px -660px; }
+.emoji-1F575 { background-position: -40px -660px; }
+.emoji-1F575-1F3FB { background-position: -60px -660px; }
+.emoji-1F575-1F3FC { background-position: -80px -660px; }
+.emoji-1F575-1F3FD { background-position: -100px -660px; }
+.emoji-1F575-1F3FE { background-position: -120px -660px; }
+.emoji-1F575-1F3FF { background-position: -140px -660px; }
+.emoji-1F576 { background-position: -160px -660px; }
+.emoji-1F577 { background-position: -180px -660px; }
+.emoji-1F578 { background-position: -200px -660px; }
+.emoji-1F579 { background-position: -220px -660px; }
+.emoji-1F57A { background-position: -240px -660px; }
+.emoji-1F57A-1F3FB { background-position: -260px -660px; }
+.emoji-1F57A-1F3FC { background-position: -280px -660px; }
+.emoji-1F57A-1F3FD { background-position: -300px -660px; }
+.emoji-1F57A-1F3FE { background-position: -320px -660px; }
+.emoji-1F57A-1F3FF { background-position: -340px -660px; }
+.emoji-1F587 { background-position: -360px -660px; }
+.emoji-1F58A { background-position: -380px -660px; }
+.emoji-1F58B { background-position: -400px -660px; }
+.emoji-1F58C { background-position: -420px -660px; }
+.emoji-1F58D { background-position: -440px -660px; }
+.emoji-1F590 { background-position: -460px -660px; }
+.emoji-1F590-1F3FB { background-position: -480px -660px; }
+.emoji-1F590-1F3FC { background-position: -500px -660px; }
+.emoji-1F590-1F3FD { background-position: -520px -660px; }
+.emoji-1F590-1F3FE { background-position: -540px -660px; }
+.emoji-1F590-1F3FF { background-position: -560px -660px; }
+.emoji-1F595 { background-position: -580px -660px; }
+.emoji-1F595-1F3FB { background-position: -600px -660px; }
+.emoji-1F595-1F3FC { background-position: -620px -660px; }
+.emoji-1F595-1F3FD { background-position: -640px -660px; }
+.emoji-1F595-1F3FE { background-position: -660px -660px; }
+.emoji-1F595-1F3FF { background-position: -680px 0; }
+.emoji-1F596 { background-position: -680px -20px; }
+.emoji-1F596-1F3FB { background-position: -680px -40px; }
+.emoji-1F596-1F3FC { background-position: -680px -60px; }
+.emoji-1F596-1F3FD { background-position: -680px -80px; }
+.emoji-1F596-1F3FE { background-position: -680px -100px; }
+.emoji-1F596-1F3FF { background-position: -680px -120px; }
+.emoji-1F5A4 { background-position: -680px -140px; }
+.emoji-1F5A5 { background-position: -680px -160px; }
+.emoji-1F5A8 { background-position: -680px -180px; }
+.emoji-1F5B1 { background-position: -680px -200px; }
+.emoji-1F5B2 { background-position: -680px -220px; }
+.emoji-1F5BC { background-position: -680px -240px; }
+.emoji-1F5C2 { background-position: -680px -260px; }
+.emoji-1F5C3 { background-position: -680px -280px; }
+.emoji-1F5C4 { background-position: -680px -300px; }
+.emoji-1F5D1 { background-position: -680px -320px; }
+.emoji-1F5D2 { background-position: -680px -340px; }
+.emoji-1F5D3 { background-position: -680px -360px; }
+.emoji-1F5DC { background-position: -680px -380px; }
+.emoji-1F5DD { background-position: -680px -400px; }
+.emoji-1F5DE { background-position: -680px -420px; }
+.emoji-1F5E1 { background-position: -680px -440px; }
+.emoji-1F5E3 { background-position: -680px -460px; }
+.emoji-1F5EF { background-position: -680px -480px; }
+.emoji-1F5F3 { background-position: -680px -500px; }
+.emoji-1F5FA { background-position: -680px -520px; }
+.emoji-1F5FB { background-position: -680px -540px; }
+.emoji-1F5FC { background-position: -680px -560px; }
+.emoji-1F5FD { background-position: -680px -580px; }
+.emoji-1F5FE { background-position: -680px -600px; }
+.emoji-1F5FF { background-position: -680px -620px; }
+.emoji-1F600 { background-position: -680px -640px; }
+.emoji-1F601 { background-position: -680px -660px; }
+.emoji-1F602 { background-position: 0 -680px; }
+.emoji-1F603 { background-position: -20px -680px; }
+.emoji-1F604 { background-position: -40px -680px; }
+.emoji-1F605 { background-position: -60px -680px; }
+.emoji-1F606 { background-position: -80px -680px; }
+.emoji-1F607 { background-position: -100px -680px; }
+.emoji-1F608 { background-position: -120px -680px; }
+.emoji-1F609 { background-position: -140px -680px; }
+.emoji-1F60A { background-position: -160px -680px; }
+.emoji-1F60B { background-position: -180px -680px; }
+.emoji-1F60C { background-position: -200px -680px; }
+.emoji-1F60D { background-position: -220px -680px; }
+.emoji-1F60E { background-position: -240px -680px; }
+.emoji-1F60F { background-position: -260px -680px; }
+.emoji-1F610 { background-position: -280px -680px; }
+.emoji-1F611 { background-position: -300px -680px; }
+.emoji-1F612 { background-position: -320px -680px; }
+.emoji-1F613 { background-position: -340px -680px; }
+.emoji-1F614 { background-position: -360px -680px; }
+.emoji-1F615 { background-position: -380px -680px; }
+.emoji-1F616 { background-position: -400px -680px; }
+.emoji-1F617 { background-position: -420px -680px; }
+.emoji-1F618 { background-position: -440px -680px; }
+.emoji-1F619 { background-position: -460px -680px; }
+.emoji-1F61A { background-position: -480px -680px; }
+.emoji-1F61B { background-position: -500px -680px; }
+.emoji-1F61C { background-position: -520px -680px; }
+.emoji-1F61D { background-position: -540px -680px; }
+.emoji-1F61E { background-position: -560px -680px; }
+.emoji-1F61F { background-position: -580px -680px; }
+.emoji-1F620 { background-position: -600px -680px; }
+.emoji-1F621 { background-position: -620px -680px; }
+.emoji-1F622 { background-position: -640px -680px; }
+.emoji-1F623 { background-position: -660px -680px; }
+.emoji-1F624 { background-position: -680px -680px; }
+.emoji-1F625 { background-position: -700px 0; }
+.emoji-1F626 { background-position: -700px -20px; }
+.emoji-1F627 { background-position: -700px -40px; }
+.emoji-1F628 { background-position: -700px -60px; }
+.emoji-1F629 { background-position: -700px -80px; }
+.emoji-1F62A { background-position: -700px -100px; }
+.emoji-1F62B { background-position: -700px -120px; }
+.emoji-1F62C { background-position: -700px -140px; }
+.emoji-1F62D { background-position: -700px -160px; }
+.emoji-1F62E { background-position: -700px -180px; }
+.emoji-1F62F { background-position: -700px -200px; }
+.emoji-1F630 { background-position: -700px -220px; }
+.emoji-1F631 { background-position: -700px -240px; }
+.emoji-1F632 { background-position: -700px -260px; }
+.emoji-1F633 { background-position: -700px -280px; }
+.emoji-1F634 { background-position: -700px -300px; }
+.emoji-1F635 { background-position: -700px -320px; }
+.emoji-1F636 { background-position: -700px -340px; }
+.emoji-1F637 { background-position: -700px -360px; }
+.emoji-1F638 { background-position: -700px -380px; }
+.emoji-1F639 { background-position: -700px -400px; }
+.emoji-1F63A { background-position: -700px -420px; }
+.emoji-1F63B { background-position: -700px -440px; }
+.emoji-1F63C { background-position: -700px -460px; }
+.emoji-1F63D { background-position: -700px -480px; }
+.emoji-1F63E { background-position: -700px -500px; }
+.emoji-1F63F { background-position: -700px -520px; }
+.emoji-1F640 { background-position: -700px -540px; }
+.emoji-1F641 { background-position: -700px -560px; }
+.emoji-1F642 { background-position: -700px -580px; }
+.emoji-1F643 { background-position: -700px -600px; }
+.emoji-1F644 { background-position: -700px -620px; }
+.emoji-1F645 { background-position: -700px -640px; }
+.emoji-1F645-1F3FB { background-position: -700px -660px; }
+.emoji-1F645-1F3FC { background-position: -700px -680px; }
+.emoji-1F645-1F3FD { background-position: 0 -700px; }
+.emoji-1F645-1F3FE { background-position: -20px -700px; }
+.emoji-1F645-1F3FF { background-position: -40px -700px; }
+.emoji-1F646 { background-position: -60px -700px; }
+.emoji-1F646-1F3FB { background-position: -80px -700px; }
+.emoji-1F646-1F3FC { background-position: -100px -700px; }
+.emoji-1F646-1F3FD { background-position: -120px -700px; }
+.emoji-1F646-1F3FE { background-position: -140px -700px; }
+.emoji-1F646-1F3FF { background-position: -160px -700px; }
+.emoji-1F647 { background-position: -180px -700px; }
+.emoji-1F647-1F3FB { background-position: -200px -700px; }
+.emoji-1F647-1F3FC { background-position: -220px -700px; }
+.emoji-1F647-1F3FD { background-position: -240px -700px; }
+.emoji-1F647-1F3FE { background-position: -260px -700px; }
+.emoji-1F647-1F3FF { background-position: -280px -700px; }
+.emoji-1F648 { background-position: -300px -700px; }
+.emoji-1F649 { background-position: -320px -700px; }
+.emoji-1F64A { background-position: -340px -700px; }
+.emoji-1F64B { background-position: -360px -700px; }
+.emoji-1F64B-1F3FB { background-position: -380px -700px; }
+.emoji-1F64B-1F3FC { background-position: -400px -700px; }
+.emoji-1F64B-1F3FD { background-position: -420px -700px; }
+.emoji-1F64B-1F3FE { background-position: -440px -700px; }
+.emoji-1F64B-1F3FF { background-position: -460px -700px; }
+.emoji-1F64C { background-position: -480px -700px; }
+.emoji-1F64C-1F3FB { background-position: -500px -700px; }
+.emoji-1F64C-1F3FC { background-position: -520px -700px; }
+.emoji-1F64C-1F3FD { background-position: -540px -700px; }
+.emoji-1F64C-1F3FE { background-position: -560px -700px; }
+.emoji-1F64C-1F3FF { background-position: -580px -700px; }
+.emoji-1F64D { background-position: -600px -700px; }
+.emoji-1F64D-1F3FB { background-position: -620px -700px; }
+.emoji-1F64D-1F3FC { background-position: -640px -700px; }
+.emoji-1F64D-1F3FD { background-position: -660px -700px; }
+.emoji-1F64D-1F3FE { background-position: -680px -700px; }
+.emoji-1F64D-1F3FF { background-position: -700px -700px; }
+.emoji-1F64E { background-position: -720px 0; }
+.emoji-1F64E-1F3FB { background-position: -720px -20px; }
+.emoji-1F64E-1F3FC { background-position: -720px -40px; }
+.emoji-1F64E-1F3FD { background-position: -720px -60px; }
+.emoji-1F64E-1F3FE { background-position: -720px -80px; }
+.emoji-1F64E-1F3FF { background-position: -720px -100px; }
+.emoji-1F64F { background-position: -720px -120px; }
+.emoji-1F64F-1F3FB { background-position: -720px -140px; }
+.emoji-1F64F-1F3FC { background-position: -720px -160px; }
+.emoji-1F64F-1F3FD { background-position: -720px -180px; }
+.emoji-1F64F-1F3FE { background-position: -720px -200px; }
+.emoji-1F64F-1F3FF { background-position: -720px -220px; }
+.emoji-1F680 { background-position: -720px -240px; }
+.emoji-1F681 { background-position: -720px -260px; }
+.emoji-1F682 { background-position: -720px -280px; }
+.emoji-1F683 { background-position: -720px -300px; }
+.emoji-1F684 { background-position: -720px -320px; }
+.emoji-1F685 { background-position: -720px -340px; }
+.emoji-1F686 { background-position: -720px -360px; }
+.emoji-1F687 { background-position: -720px -380px; }
+.emoji-1F688 { background-position: -720px -400px; }
+.emoji-1F689 { background-position: -720px -420px; }
+.emoji-1F68A { background-position: -720px -440px; }
+.emoji-1F68B { background-position: -720px -460px; }
+.emoji-1F68C { background-position: -720px -480px; }
+.emoji-1F68D { background-position: -720px -500px; }
+.emoji-1F68E { background-position: -720px -520px; }
+.emoji-1F68F { background-position: -720px -540px; }
+.emoji-1F690 { background-position: -720px -560px; }
+.emoji-1F691 { background-position: -720px -580px; }
+.emoji-1F692 { background-position: -720px -600px; }
+.emoji-1F693 { background-position: -720px -620px; }
+.emoji-1F694 { background-position: -720px -640px; }
+.emoji-1F695 { background-position: -720px -660px; }
+.emoji-1F696 { background-position: -720px -680px; }
+.emoji-1F697 { background-position: -720px -700px; }
+.emoji-1F698 { background-position: 0 -720px; }
+.emoji-1F699 { background-position: -20px -720px; }
+.emoji-1F69A { background-position: -40px -720px; }
+.emoji-1F69B { background-position: -60px -720px; }
+.emoji-1F69C { background-position: -80px -720px; }
+.emoji-1F69D { background-position: -100px -720px; }
+.emoji-1F69E { background-position: -120px -720px; }
+.emoji-1F69F { background-position: -140px -720px; }
+.emoji-1F6A0 { background-position: -160px -720px; }
+.emoji-1F6A1 { background-position: -180px -720px; }
+.emoji-1F6A2 { background-position: -200px -720px; }
+.emoji-1F6A3 { background-position: -220px -720px; }
+.emoji-1F6A3-1F3FB { background-position: -240px -720px; }
+.emoji-1F6A3-1F3FC { background-position: -260px -720px; }
+.emoji-1F6A3-1F3FD { background-position: -280px -720px; }
+.emoji-1F6A3-1F3FE { background-position: -300px -720px; }
+.emoji-1F6A3-1F3FF { background-position: -320px -720px; }
+.emoji-1F6A4 { background-position: -340px -720px; }
+.emoji-1F6A5 { background-position: -360px -720px; }
+.emoji-1F6A6 { background-position: -380px -720px; }
+.emoji-1F6A7 { background-position: -400px -720px; }
+.emoji-1F6A8 { background-position: -420px -720px; }
+.emoji-1F6A9 { background-position: -440px -720px; }
+.emoji-1F6AA { background-position: -460px -720px; }
+.emoji-1F6AB { background-position: -480px -720px; }
+.emoji-1F6AC { background-position: -500px -720px; }
+.emoji-1F6AD { background-position: -520px -720px; }
+.emoji-1F6AE { background-position: -540px -720px; }
+.emoji-1F6AF { background-position: -560px -720px; }
+.emoji-1F6B0 { background-position: -580px -720px; }
+.emoji-1F6B1 { background-position: -600px -720px; }
+.emoji-1F6B2 { background-position: -620px -720px; }
+.emoji-1F6B3 { background-position: -640px -720px; }
+.emoji-1F6B4 { background-position: -660px -720px; }
+.emoji-1F6B4-1F3FB { background-position: -680px -720px; }
+.emoji-1F6B4-1F3FC { background-position: -700px -720px; }
+.emoji-1F6B4-1F3FD { background-position: -720px -720px; }
+.emoji-1F6B4-1F3FE { background-position: -740px 0; }
+.emoji-1F6B4-1F3FF { background-position: -740px -20px; }
+.emoji-1F6B5 { background-position: -740px -40px; }
+.emoji-1F6B5-1F3FB { background-position: -740px -60px; }
+.emoji-1F6B5-1F3FC { background-position: -740px -80px; }
+.emoji-1F6B5-1F3FD { background-position: -740px -100px; }
+.emoji-1F6B5-1F3FE { background-position: -740px -120px; }
+.emoji-1F6B5-1F3FF { background-position: -740px -140px; }
+.emoji-1F6B6 { background-position: -740px -160px; }
+.emoji-1F6B6-1F3FB { background-position: -740px -180px; }
+.emoji-1F6B6-1F3FC { background-position: -740px -200px; }
+.emoji-1F6B6-1F3FD { background-position: -740px -220px; }
+.emoji-1F6B6-1F3FE { background-position: -740px -240px; }
+.emoji-1F6B6-1F3FF { background-position: -740px -260px; }
+.emoji-1F6B7 { background-position: -740px -280px; }
+.emoji-1F6B8 { background-position: -740px -300px; }
+.emoji-1F6B9 { background-position: -740px -320px; }
+.emoji-1F6BA { background-position: -740px -340px; }
+.emoji-1F6BB { background-position: -740px -360px; }
+.emoji-1F6BC { background-position: -740px -380px; }
+.emoji-1F6BD { background-position: -740px -400px; }
+.emoji-1F6BE { background-position: -740px -420px; }
+.emoji-1F6BF { background-position: -740px -440px; }
+.emoji-1F6C0 { background-position: -740px -460px; }
+.emoji-1F6C0-1F3FB { background-position: -740px -480px; }
+.emoji-1F6C0-1F3FC { background-position: -740px -500px; }
+.emoji-1F6C0-1F3FD { background-position: -740px -520px; }
+.emoji-1F6C0-1F3FE { background-position: -740px -540px; }
+.emoji-1F6C0-1F3FF { background-position: -740px -560px; }
+.emoji-1F6C1 { background-position: -740px -580px; }
+.emoji-1F6C2 { background-position: -740px -600px; }
+.emoji-1F6C3 { background-position: -740px -620px; }
+.emoji-1F6C4 { background-position: -740px -640px; }
+.emoji-1F6C5 { background-position: -740px -660px; }
+.emoji-1F6CB { background-position: -740px -680px; }
+.emoji-1F6CC { background-position: -740px -700px; }
+.emoji-1F6CD { background-position: -740px -720px; }
+.emoji-1F6CE { background-position: 0 -740px; }
+.emoji-1F6CF { background-position: -20px -740px; }
+.emoji-1F6D0 { background-position: -40px -740px; }
+.emoji-1F6D1 { background-position: -60px -740px; }
+.emoji-1F6D2 { background-position: -80px -740px; }
+.emoji-1F6E0 { background-position: -100px -740px; }
+.emoji-1F6E1 { background-position: -120px -740px; }
+.emoji-1F6E2 { background-position: -140px -740px; }
+.emoji-1F6E3 { background-position: -160px -740px; }
+.emoji-1F6E4 { background-position: -180px -740px; }
+.emoji-1F6E5 { background-position: -200px -740px; }
+.emoji-1F6E9 { background-position: -220px -740px; }
+.emoji-1F6EB { background-position: -240px -740px; }
+.emoji-1F6EC { background-position: -260px -740px; }
+.emoji-1F6F0 { background-position: -280px -740px; }
+.emoji-1F6F3 { background-position: -300px -740px; }
+.emoji-1F6F4 { background-position: -320px -740px; }
+.emoji-1F6F5 { background-position: -340px -740px; }
+.emoji-1F6F6 { background-position: -360px -740px; }
+.emoji-1F910 { background-position: -380px -740px; }
+.emoji-1F911 { background-position: -400px -740px; }
+.emoji-1F912 { background-position: -420px -740px; }
+.emoji-1F913 { background-position: -440px -740px; }
+.emoji-1F914 { background-position: -460px -740px; }
+.emoji-1F915 { background-position: -480px -740px; }
+.emoji-1F916 { background-position: -500px -740px; }
+.emoji-1F917 { background-position: -520px -740px; }
+.emoji-1F918 { background-position: -540px -740px; }
+.emoji-1F918-1F3FB { background-position: -560px -740px; }
+.emoji-1F918-1F3FC { background-position: -580px -740px; }
+.emoji-1F918-1F3FD { background-position: -600px -740px; }
+.emoji-1F918-1F3FE { background-position: -620px -740px; }
+.emoji-1F918-1F3FF { background-position: -640px -740px; }
+.emoji-1F919 { background-position: -660px -740px; }
+.emoji-1F919-1F3FB { background-position: -680px -740px; }
+.emoji-1F919-1F3FC { background-position: -700px -740px; }
+.emoji-1F919-1F3FD { background-position: -720px -740px; }
+.emoji-1F919-1F3FE { background-position: -740px -740px; }
+.emoji-1F919-1F3FF { background-position: -760px 0; }
+.emoji-1F91A { background-position: -760px -20px; }
+.emoji-1F91A-1F3FB { background-position: -760px -40px; }
+.emoji-1F91A-1F3FC { background-position: -760px -60px; }
+.emoji-1F91A-1F3FD { background-position: -760px -80px; }
+.emoji-1F91A-1F3FE { background-position: -760px -100px; }
+.emoji-1F91A-1F3FF { background-position: -760px -120px; }
+.emoji-1F91B { background-position: -760px -140px; }
+.emoji-1F91B-1F3FB { background-position: -760px -160px; }
+.emoji-1F91B-1F3FC { background-position: -760px -180px; }
+.emoji-1F91B-1F3FD { background-position: -760px -200px; }
+.emoji-1F91B-1F3FE { background-position: -760px -220px; }
+.emoji-1F91B-1F3FF { background-position: -760px -240px; }
+.emoji-1F91C { background-position: -760px -260px; }
+.emoji-1F91C-1F3FB { background-position: -760px -280px; }
+.emoji-1F91C-1F3FC { background-position: -760px -300px; }
+.emoji-1F91C-1F3FD { background-position: -760px -320px; }
+.emoji-1F91C-1F3FE { background-position: -760px -340px; }
+.emoji-1F91C-1F3FF { background-position: -760px -360px; }
+.emoji-1F91D { background-position: -760px -380px; }
+.emoji-1F91D-1F3FB { background-position: -760px -400px; }
+.emoji-1F91D-1F3FC { background-position: -760px -420px; }
+.emoji-1F91D-1F3FD { background-position: -760px -440px; }
+.emoji-1F91D-1F3FE { background-position: -760px -460px; }
+.emoji-1F91D-1F3FF { background-position: -760px -480px; }
+.emoji-1F91E { background-position: -760px -500px; }
+.emoji-1F91E-1F3FB { background-position: -760px -520px; }
+.emoji-1F91E-1F3FC { background-position: -760px -540px; }
+.emoji-1F91E-1F3FD { background-position: -760px -560px; }
+.emoji-1F91E-1F3FE { background-position: -760px -580px; }
+.emoji-1F91E-1F3FF { background-position: -760px -600px; }
+.emoji-1F920 { background-position: -760px -620px; }
+.emoji-1F921 { background-position: -760px -640px; }
+.emoji-1F922 { background-position: -760px -660px; }
+.emoji-1F923 { background-position: -760px -680px; }
+.emoji-1F924 { background-position: -760px -700px; }
+.emoji-1F925 { background-position: -760px -720px; }
+.emoji-1F926 { background-position: -760px -740px; }
+.emoji-1F926-1F3FB { background-position: 0 -760px; }
+.emoji-1F926-1F3FC { background-position: -20px -760px; }
+.emoji-1F926-1F3FD { background-position: -40px -760px; }
+.emoji-1F926-1F3FE { background-position: -60px -760px; }
+.emoji-1F926-1F3FF { background-position: -80px -760px; }
+.emoji-1F927 { background-position: -100px -760px; }
+.emoji-1F930 { background-position: -120px -760px; }
+.emoji-1F930-1F3FB { background-position: -140px -760px; }
+.emoji-1F930-1F3FC { background-position: -160px -760px; }
+.emoji-1F930-1F3FD { background-position: -180px -760px; }
+.emoji-1F930-1F3FE { background-position: -200px -760px; }
+.emoji-1F930-1F3FF { background-position: -220px -760px; }
+.emoji-1F933 { background-position: -240px -760px; }
+.emoji-1F933-1F3FB { background-position: -260px -760px; }
+.emoji-1F933-1F3FC { background-position: -280px -760px; }
+.emoji-1F933-1F3FD { background-position: -300px -760px; }
+.emoji-1F933-1F3FE { background-position: -320px -760px; }
+.emoji-1F933-1F3FF { background-position: -340px -760px; }
+.emoji-1F934 { background-position: -360px -760px; }
+.emoji-1F934-1F3FB { background-position: -380px -760px; }
+.emoji-1F934-1F3FC { background-position: -400px -760px; }
+.emoji-1F934-1F3FD { background-position: -420px -760px; }
+.emoji-1F934-1F3FE { background-position: -440px -760px; }
+.emoji-1F934-1F3FF { background-position: -460px -760px; }
+.emoji-1F935 { background-position: -480px -760px; }
+.emoji-1F935-1F3FB { background-position: -500px -760px; }
+.emoji-1F935-1F3FC { background-position: -520px -760px; }
+.emoji-1F935-1F3FD { background-position: -540px -760px; }
+.emoji-1F935-1F3FE { background-position: -560px -760px; }
+.emoji-1F935-1F3FF { background-position: -580px -760px; }
+.emoji-1F936 { background-position: -600px -760px; }
+.emoji-1F936-1F3FB { background-position: -620px -760px; }
+.emoji-1F936-1F3FC { background-position: -640px -760px; }
+.emoji-1F936-1F3FD { background-position: -660px -760px; }
+.emoji-1F936-1F3FE { background-position: -680px -760px; }
+.emoji-1F936-1F3FF { background-position: -700px -760px; }
+.emoji-1F937 { background-position: -720px -760px; }
+.emoji-1F937-1F3FB { background-position: -740px -760px; }
+.emoji-1F937-1F3FC { background-position: -760px -760px; }
+.emoji-1F937-1F3FD { background-position: -780px 0; }
+.emoji-1F937-1F3FE { background-position: -780px -20px; }
+.emoji-1F937-1F3FF { background-position: -780px -40px; }
+.emoji-1F938 { background-position: -780px -60px; }
+.emoji-1F938-1F3FB { background-position: -780px -80px; }
+.emoji-1F938-1F3FC { background-position: -780px -100px; }
+.emoji-1F938-1F3FD { background-position: -780px -120px; }
+.emoji-1F938-1F3FE { background-position: -780px -140px; }
+.emoji-1F938-1F3FF { background-position: -780px -160px; }
+.emoji-1F939 { background-position: -780px -180px; }
+.emoji-1F939-1F3FB { background-position: -780px -200px; }
+.emoji-1F939-1F3FC { background-position: -780px -220px; }
+.emoji-1F939-1F3FD { background-position: -780px -240px; }
+.emoji-1F939-1F3FE { background-position: -780px -260px; }
+.emoji-1F939-1F3FF { background-position: -780px -280px; }
+.emoji-1F93A { background-position: -780px -300px; }
+.emoji-1F93C { background-position: -780px -320px; }
+.emoji-1F93C-1F3FB { background-position: -780px -340px; }
+.emoji-1F93C-1F3FC { background-position: -780px -360px; }
+.emoji-1F93C-1F3FD { background-position: -780px -380px; }
+.emoji-1F93C-1F3FE { background-position: -780px -400px; }
+.emoji-1F93C-1F3FF { background-position: -780px -420px; }
+.emoji-1F93D { background-position: -780px -440px; }
+.emoji-1F93D-1F3FB { background-position: -780px -460px; }
+.emoji-1F93D-1F3FC { background-position: -780px -480px; }
+.emoji-1F93D-1F3FD { background-position: -780px -500px; }
+.emoji-1F93D-1F3FE { background-position: -780px -520px; }
+.emoji-1F93D-1F3FF { background-position: -780px -540px; }
+.emoji-1F93E { background-position: -780px -560px; }
+.emoji-1F93E-1F3FB { background-position: -780px -580px; }
+.emoji-1F93E-1F3FC { background-position: -780px -600px; }
+.emoji-1F93E-1F3FD { background-position: -780px -620px; }
+.emoji-1F93E-1F3FE { background-position: -780px -640px; }
+.emoji-1F93E-1F3FF { background-position: -780px -660px; }
+.emoji-1F940 { background-position: -780px -680px; }
+.emoji-1F941 { background-position: -780px -700px; }
+.emoji-1F942 { background-position: -780px -720px; }
+.emoji-1F943 { background-position: -780px -740px; }
+.emoji-1F944 { background-position: -780px -760px; }
+.emoji-1F945 { background-position: 0 -780px; }
+.emoji-1F947 { background-position: -20px -780px; }
+.emoji-1F948 { background-position: -40px -780px; }
+.emoji-1F949 { background-position: -60px -780px; }
+.emoji-1F94A { background-position: -80px -780px; }
+.emoji-1F94B { background-position: -100px -780px; }
+.emoji-1F950 { background-position: -120px -780px; }
+.emoji-1F951 { background-position: -140px -780px; }
+.emoji-1F952 { background-position: -160px -780px; }
+.emoji-1F953 { background-position: -180px -780px; }
+.emoji-1F954 { background-position: -200px -780px; }
+.emoji-1F955 { background-position: -220px -780px; }
+.emoji-1F956 { background-position: -240px -780px; }
+.emoji-1F957 { background-position: -260px -780px; }
+.emoji-1F958 { background-position: -280px -780px; }
+.emoji-1F959 { background-position: -300px -780px; }
+.emoji-1F95A { background-position: -320px -780px; }
+.emoji-1F95B { background-position: -340px -780px; }
+.emoji-1F95C { background-position: -360px -780px; }
+.emoji-1F95D { background-position: -380px -780px; }
+.emoji-1F95E { background-position: -400px -780px; }
+.emoji-1F980 { background-position: -420px -780px; }
+.emoji-1F981 { background-position: -440px -780px; }
+.emoji-1F982 { background-position: -460px -780px; }
+.emoji-1F983 { background-position: -480px -780px; }
+.emoji-1F984 { background-position: -500px -780px; }
+.emoji-1F985 { background-position: -520px -780px; }
+.emoji-1F986 { background-position: -540px -780px; }
+.emoji-1F987 { background-position: -560px -780px; }
+.emoji-1F988 { background-position: -580px -780px; }
+.emoji-1F989 { background-position: -600px -780px; }
+.emoji-1F98A { background-position: -620px -780px; }
+.emoji-1F98B { background-position: -640px -780px; }
+.emoji-1F98C { background-position: -660px -780px; }
+.emoji-1F98D { background-position: -680px -780px; }
+.emoji-1F98E { background-position: -700px -780px; }
+.emoji-1F98F { background-position: -720px -780px; }
+.emoji-1F990 { background-position: -740px -780px; }
+.emoji-1F991 { background-position: -760px -780px; }
+.emoji-1F9C0 { background-position: -780px -780px; }
+.emoji-203C { background-position: -800px 0; }
+.emoji-2049 { background-position: -800px -20px; }
+.emoji-2122 { background-position: -800px -40px; }
+.emoji-2139 { background-position: -800px -60px; }
+.emoji-2194 { background-position: -800px -80px; }
+.emoji-2195 { background-position: -800px -100px; }
+.emoji-2196 { background-position: -800px -120px; }
+.emoji-2197 { background-position: -800px -140px; }
+.emoji-2198 { background-position: -800px -160px; }
+.emoji-2199 { background-position: -800px -180px; }
+.emoji-21A9 { background-position: -800px -200px; }
+.emoji-21AA { background-position: -800px -220px; }
+.emoji-231A { background-position: -800px -240px; }
+.emoji-231B { background-position: -800px -260px; }
+.emoji-2328 { background-position: -800px -280px; }
+.emoji-23CF { background-position: -800px -300px; }
+.emoji-23E9 { background-position: -800px -320px; }
+.emoji-23EA { background-position: -800px -340px; }
+.emoji-23EB { background-position: -800px -360px; }
+.emoji-23EC { background-position: -800px -380px; }
+.emoji-23ED { background-position: -800px -400px; }
+.emoji-23EE { background-position: -800px -420px; }
+.emoji-23EF { background-position: -800px -440px; }
+.emoji-23F0 { background-position: -800px -460px; }
+.emoji-23F1 { background-position: -800px -480px; }
+.emoji-23F2 { background-position: -800px -500px; }
+.emoji-23F3 { background-position: -800px -520px; }
+.emoji-23F8 { background-position: -800px -540px; }
+.emoji-23F9 { background-position: -800px -560px; }
+.emoji-23FA { background-position: -800px -580px; }
+.emoji-24C2 { background-position: -800px -600px; }
+.emoji-25AA { background-position: -800px -620px; }
+.emoji-25AB { background-position: -800px -640px; }
+.emoji-25B6 { background-position: -800px -660px; }
+.emoji-25C0 { background-position: -800px -680px; }
+.emoji-25FB { background-position: -800px -700px; }
+.emoji-25FC { background-position: -800px -720px; }
+.emoji-25FD { background-position: -800px -740px; }
+.emoji-25FE { background-position: -800px -760px; }
+.emoji-2600 { background-position: -800px -780px; }
+.emoji-2601 { background-position: 0 -800px; }
+.emoji-2602 { background-position: -20px -800px; }
+.emoji-2603 { background-position: -40px -800px; }
+.emoji-2604 { background-position: -60px -800px; }
+.emoji-260E { background-position: -80px -800px; }
+.emoji-2611 { background-position: -100px -800px; }
+.emoji-2614 { background-position: -120px -800px; }
+.emoji-2615 { background-position: -140px -800px; }
+.emoji-2618 { background-position: -160px -800px; }
+.emoji-261D { background-position: -180px -800px; }
+.emoji-261D-1F3FB { background-position: -200px -800px; }
+.emoji-261D-1F3FC { background-position: -220px -800px; }
+.emoji-261D-1F3FD { background-position: -240px -800px; }
+.emoji-261D-1F3FE { background-position: -260px -800px; }
+.emoji-261D-1F3FF { background-position: -280px -800px; }
+.emoji-2620 { background-position: -300px -800px; }
+.emoji-2622 { background-position: -320px -800px; }
+.emoji-2623 { background-position: -340px -800px; }
+.emoji-2626 { background-position: -360px -800px; }
+.emoji-262A { background-position: -380px -800px; }
+.emoji-262E { background-position: -400px -800px; }
+.emoji-262F { background-position: -420px -800px; }
+.emoji-2638 { background-position: -440px -800px; }
+.emoji-2639 { background-position: -460px -800px; }
+.emoji-263A { background-position: -480px -800px; }
+.emoji-2648 { background-position: -500px -800px; }
+.emoji-2649 { background-position: -520px -800px; }
+.emoji-264A { background-position: -540px -800px; }
+.emoji-264B { background-position: -560px -800px; }
+.emoji-264C { background-position: -580px -800px; }
+.emoji-264D { background-position: -600px -800px; }
+.emoji-264E { background-position: -620px -800px; }
+.emoji-264F { background-position: -640px -800px; }
+.emoji-2650 { background-position: -660px -800px; }
+.emoji-2651 { background-position: -680px -800px; }
+.emoji-2652 { background-position: -700px -800px; }
+.emoji-2653 { background-position: -720px -800px; }
+.emoji-2660 { background-position: -740px -800px; }
+.emoji-2663 { background-position: -760px -800px; }
+.emoji-2665 { background-position: -780px -800px; }
+.emoji-2666 { background-position: -800px -800px; }
+.emoji-2668 { background-position: -820px 0; }
+.emoji-267B { background-position: -820px -20px; }
+.emoji-267F { background-position: -820px -40px; }
+.emoji-2692 { background-position: -820px -60px; }
+.emoji-2693 { background-position: -820px -80px; }
+.emoji-2694 { background-position: -820px -100px; }
+.emoji-2696 { background-position: -820px -120px; }
+.emoji-2697 { background-position: -820px -140px; }
+.emoji-2699 { background-position: -820px -160px; }
+.emoji-269B { background-position: -820px -180px; }
+.emoji-269C { background-position: -820px -200px; }
+.emoji-26A0 { background-position: -820px -220px; }
+.emoji-26A1 { background-position: -820px -240px; }
+.emoji-26AA { background-position: -820px -260px; }
+.emoji-26AB { background-position: -820px -280px; }
+.emoji-26B0 { background-position: -820px -300px; }
+.emoji-26B1 { background-position: -820px -320px; }
+.emoji-26BD { background-position: -820px -340px; }
+.emoji-26BE { background-position: -820px -360px; }
+.emoji-26C4 { background-position: -820px -380px; }
+.emoji-26C5 { background-position: -820px -400px; }
+.emoji-26C8 { background-position: -820px -420px; }
+.emoji-26CE { background-position: -820px -440px; }
+.emoji-26CF { background-position: -820px -460px; }
+.emoji-26D1 { background-position: -820px -480px; }
+.emoji-26D3 { background-position: -820px -500px; }
+.emoji-26D4 { background-position: -820px -520px; }
+.emoji-26E9 { background-position: -820px -540px; }
+.emoji-26EA { background-position: -820px -560px; }
+.emoji-26F0 { background-position: -820px -580px; }
+.emoji-26F1 { background-position: -820px -600px; }
+.emoji-26F2 { background-position: -820px -620px; }
+.emoji-26F3 { background-position: -820px -640px; }
+.emoji-26F4 { background-position: -820px -660px; }
+.emoji-26F5 { background-position: -820px -680px; }
+.emoji-26F7 { background-position: -820px -700px; }
+.emoji-26F8 { background-position: -820px -720px; }
+.emoji-26F9 { background-position: -820px -740px; }
+.emoji-26F9-1F3FB { background-position: -820px -760px; }
+.emoji-26F9-1F3FC { background-position: -820px -780px; }
+.emoji-26F9-1F3FD { background-position: -820px -800px; }
+.emoji-26F9-1F3FE { background-position: 0 -820px; }
+.emoji-26F9-1F3FF { background-position: -20px -820px; }
+.emoji-26FA { background-position: -40px -820px; }
+.emoji-26FD { background-position: -60px -820px; }
+.emoji-2702 { background-position: -80px -820px; }
+.emoji-2705 { background-position: -100px -820px; }
+.emoji-2708 { background-position: -120px -820px; }
+.emoji-2709 { background-position: -140px -820px; }
+.emoji-270A { background-position: -160px -820px; }
+.emoji-270A-1F3FB { background-position: -180px -820px; }
+.emoji-270A-1F3FC { background-position: -200px -820px; }
+.emoji-270A-1F3FD { background-position: -220px -820px; }
+.emoji-270A-1F3FE { background-position: -240px -820px; }
+.emoji-270A-1F3FF { background-position: -260px -820px; }
+.emoji-270B { background-position: -280px -820px; }
+.emoji-270B-1F3FB { background-position: -300px -820px; }
+.emoji-270B-1F3FC { background-position: -320px -820px; }
+.emoji-270B-1F3FD { background-position: -340px -820px; }
+.emoji-270B-1F3FE { background-position: -360px -820px; }
+.emoji-270B-1F3FF { background-position: -380px -820px; }
+.emoji-270C { background-position: -400px -820px; }
+.emoji-270C-1F3FB { background-position: -420px -820px; }
+.emoji-270C-1F3FC { background-position: -440px -820px; }
+.emoji-270C-1F3FD { background-position: -460px -820px; }
+.emoji-270C-1F3FE { background-position: -480px -820px; }
+.emoji-270C-1F3FF { background-position: -500px -820px; }
+.emoji-270D { background-position: -520px -820px; }
+.emoji-270D-1F3FB { background-position: -540px -820px; }
+.emoji-270D-1F3FC { background-position: -560px -820px; }
+.emoji-270D-1F3FD { background-position: -580px -820px; }
+.emoji-270D-1F3FE { background-position: -600px -820px; }
+.emoji-270D-1F3FF { background-position: -620px -820px; }
+.emoji-270F { background-position: -640px -820px; }
+.emoji-2712 { background-position: -660px -820px; }
+.emoji-2714 { background-position: -680px -820px; }
+.emoji-2716 { background-position: -700px -820px; }
+.emoji-271D { background-position: -720px -820px; }
+.emoji-2721 { background-position: -740px -820px; }
+.emoji-2728 { background-position: -760px -820px; }
+.emoji-2733 { background-position: -780px -820px; }
+.emoji-2734 { background-position: -800px -820px; }
+.emoji-2744 { background-position: -820px -820px; }
+.emoji-2747 { background-position: -840px 0; }
+.emoji-274C { background-position: -840px -20px; }
+.emoji-274E { background-position: -840px -40px; }
+.emoji-2753 { background-position: -840px -60px; }
+.emoji-2754 { background-position: -840px -80px; }
+.emoji-2755 { background-position: -840px -100px; }
+.emoji-2757 { background-position: -840px -120px; }
+.emoji-2763 { background-position: -840px -140px; }
+.emoji-2764 { background-position: -840px -160px; }
+.emoji-2795 { background-position: -840px -180px; }
+.emoji-2796 { background-position: -840px -200px; }
+.emoji-2797 { background-position: -840px -220px; }
+.emoji-27A1 { background-position: -840px -240px; }
+.emoji-27B0 { background-position: -840px -260px; }
+.emoji-27BF { background-position: -840px -280px; }
+.emoji-2934 { background-position: -840px -300px; }
+.emoji-2935 { background-position: -840px -320px; }
+.emoji-2B05 { background-position: -840px -340px; }
+.emoji-2B06 { background-position: -840px -360px; }
+.emoji-2B07 { background-position: -840px -380px; }
+.emoji-2B1B { background-position: -840px -400px; }
+.emoji-2B1C { background-position: -840px -420px; }
+.emoji-2B50 { background-position: -840px -440px; }
+.emoji-2B55 { background-position: -840px -460px; }
+.emoji-3030 { background-position: -840px -480px; }
+.emoji-303D { background-position: -840px -500px; }
+.emoji-3297 { background-position: -840px -520px; }
+.emoji-3299 { background-position: -840px -540px; }
.emoji-icon {
background-image: image-url('emoji.png');
@@ -1731,6 +1804,6 @@
only screen and (min-resolution: 192dpi),
only screen and (min-resolution: 2dppx) {
background-image: image-url('emoji@2x.png');
- background-size: 840px 820px;
+ background-size: 860px 840px;
}
}
diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss
index 6c36f603daf..a2145956eb5 100644
--- a/app/assets/stylesheets/pages/events.scss
+++ b/app/assets/stylesheets/pages/events.scss
@@ -54,6 +54,10 @@
}
}
+ code {
+ white-space: pre-wrap;
+ }
+
pre {
border: none;
background: #f9f9f9;
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index ac7721cbe15..2a3acc3eb4c 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -40,19 +40,24 @@
}
}
-.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 4a95b7b852e..00ab42bec5c 100644
--- a/app/assets/stylesheets/pages/help.scss
+++ b/app/assets/stylesheets/pages/help.scss
@@ -57,4 +57,12 @@
.documentation {
padding: 7px;
+
+ // Border around images in the help pages.
+ img:not(.emoji) {
+ 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 687117233f6..ded437625ee 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -4,6 +4,14 @@
margin-right: 1px;
}
}
+
+ // Border around images in issue and MR descriptions.
+ .description img:not(.emoji) {
+ border: 1px solid $table-border-gray;
+ padding: 5px;
+ margin: 5px;
+ max-height: calc(100vh - 100px);
+ }
}
.issuable-filter-count {
@@ -114,7 +122,8 @@
button {
float: right;
- padding: 3px 5px;
+ padding: 1px 5px;
+ background-color: $gray-light;
}
}
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 4e35ca329e4..ee3b2d2b801 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -63,8 +63,8 @@ form.edit-issue {
.merge-request,
.issue {
&.today {
- background: #efe;
- border-color: #cec;
+ background: #f3fff2;
+ border-color: #e1e8d5;
}
&.closed {
@@ -78,6 +78,14 @@ form.edit-issue {
}
}
+.merge-request-ci-status {
+ svg {
+ margin-right: 4px;
+ position: relative;
+ top: 1px;
+ }
+}
+
@media (max-width: $screen-xs-max) {
.issue-btn-group {
width: 100%;
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index 046c38aba44..3b1e38fc07d 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -6,6 +6,7 @@
height: 30px;
display: inline-block;
margin-right: 10px;
+ margin-bottom: 10px;
}
&.suggest-colors-dropdown {
@@ -50,11 +51,10 @@
.label-row {
.label-name {
- display: block;
+ display: inline-block;
margin-bottom: 10px;
@media (min-width: $screen-sm-min) {
- display: inline-block;
width: 200px;
margin-bottom: 0;
}
@@ -63,6 +63,7 @@
.label-description {
display: block;
margin-bottom: 10px;
+ margin-left: 50px;
@media (min-width: $screen-sm-min) {
display: inline-block;
@@ -161,9 +162,15 @@
}
.filtered-labels {
+ font-size: 0;
+ padding: 12px 16px;
+
.label-row {
+ margin-top: 4px;
+ margin-bottom: 4px;
+
&:not(:last-child) {
- margin-right: 5px;
+ margin-right: 8px;
}
}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index e67271adfb1..5254faf723d 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -60,8 +60,10 @@
.ci_widget {
border-bottom: 1px solid #eef0f2;
- i {
+ svg {
margin-right: 4px;
+ position: relative;
+ top: 1px;
}
&.ci-success {
@@ -73,11 +75,14 @@
color: #888;
}
- &.ci-pending,
- &.ci-running {
+ &.ci-pending {
color: $gl-warning;
}
+ &.ci-running {
+ color: $blue-normal;
+ }
+
&.ci-failed,
&.ci-error {
color: $gl-danger;
@@ -119,7 +124,12 @@
margin-bottom: 0;
}
- @media (max-width: $screen-sm-max) {
+ .btn-grouped {
+ margin-left: 0;
+ margin-right: 7px;
+ }
+
+ @media (max-width: $screen-xs-max) {
h4 {
font-size: 15px;
}
@@ -131,10 +141,14 @@
.btn,
.btn-group,
.accept-action {
- width: 100%;
margin-bottom: 4px;
}
+ .accept-action {
+ width: 100%;
+ text-align: center;
+ }
+
.accept-control {
width: 100%;
text-align: center;
@@ -158,7 +172,8 @@
.commit {
margin: 0;
- padding: 2px 0;
+ padding-top: 2px;
+ padding-bottom: 2px;
list-style: none;
&:hover {
background: none;
@@ -183,6 +198,16 @@
.merge-request-title {
margin-bottom: 2px;
+
+ .ci-status-link {
+
+ svg {
+ height: 16px;
+ width: 16px;
+ position: relative;
+ top: 3px;
+ }
+ }
}
}
@@ -255,8 +280,15 @@
margin-bottom: 4px;
}
+ .item-title {
+ @media (min-width: $screen-sm-min) {
+ width: 45%;
+ }
+ }
+
.avatar {
- margin-left: 0;
+ left: 0;
+ top: 2px;
}
.commit-row-info {
@@ -284,7 +316,7 @@
margin-bottom: 0;
}
- @media (min-width: $screen-sm-min) {
+ @media (min-width: $screen-xs-min) {
float: left;
width: 50%;
margin-bottom: 0;
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 577dddae741..3784010348a 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -179,6 +179,10 @@
border-top: 1px solid $border-color;
}
+.md-helper {
+ padding-top: 10px;
+}
+
.toolbar-button {
padding: 0;
background: none;
@@ -219,3 +223,16 @@
float: left;
}
}
+
+.note-form-actions {
+ @media (max-width: $screen-xs-max) {
+ .btn {
+ float: none;
+ width: 100%;
+
+ &:not(:last-child) {
+ margin-bottom: 10px;
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 35d728aec83..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;
+ }
}
}
}
@@ -84,24 +88,14 @@ ul.notes {
word-wrap: break-word;
@include md-typography;
+ // Reset ul style types since we're nested inside a ul already
+ @include bulleted-list;
+
// On diffs code should wrap nicely and not overflow
code {
white-space: pre-wrap;
}
- // Reset ul style types since we're nested inside a ul already
- & > ul {
- list-style-type: disc;
-
- ul {
- list-style-type: circle;
-
- ul {
- list-style-type: square;
- }
- }
- }
-
ul.task-list {
ul:not(.task-list) {
padding-left: 1.3em;
@@ -117,6 +111,14 @@ ul.notes {
code {
word-break: keep-all;
}
+
+ // Border around images in issue and MR comments.
+ img:not(.emoji) {
+ border: 1px solid $table-border-gray;
+ padding: 5px;
+ margin: 5px 0;
+ max-height: calc(100vh - 100px);
+ }
}
}
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 6128868b670..a404f108dc4 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -1,15 +1,12 @@
.pipelines {
.stage {
- max-width: 100px;
+ max-width: 90px;
+ width: 90px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
- .duration, .finished_at {
- margin: 4px 0;
- }
-
.commit-title {
margin: 0;
}
@@ -22,3 +19,195 @@
margin: 4px;
}
}
+
+.content-list {
+
+ &.pipelines,
+ &.builds-content-list {
+ width: 100%;
+ overflow: auto;
+ }
+}
+
+.table.builds {
+ min-width: 1200px;
+
+ tr {
+ th {
+ padding: 16px 8px;
+ border: none;
+ }
+
+ td {
+ padding: 10px 8px;
+ }
+ }
+
+ tbody {
+ border-top-width: 1px;
+ }
+
+ .commit-link {
+
+ .ci-status {
+
+ svg {
+ top: 1px;
+ margin-right: 0;
+ }
+ }
+
+ a:hover {
+ text-decoration: none;
+ }
+ }
+
+ .branch-commit {
+
+ .branch-name {
+ font-weight: bold;
+ max-width: 150px;
+ overflow: hidden;
+ display: inline-block;
+ white-space: nowrap;
+ vertical-align: top;
+ text-overflow: ellipsis;
+ }
+
+ svg {
+ height: 14px;
+ width: auto;
+ vertical-align: middle;
+ fill: $table-text-gray;
+ }
+
+ .fa {
+ font-size: 12px;
+ color: $table-text-gray;
+ }
+
+ .commit-id {
+ color: $gl-link-color;
+ margin-right: 8px;
+ }
+
+ .commit-title {
+ margin-top: 4px;
+ max-width: 320px;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ }
+
+ .avatar {
+ margin-left: 0;
+ }
+
+ .label {
+ margin-right: 4px;
+ }
+
+ .label-container {
+ font-size: 0;
+
+ .label {
+ margin-top: 5px;
+ }
+ }
+ }
+
+ .icon-container {
+ display: inline-block;
+ text-align: right;
+ width: 20px;
+
+ .fa {
+ position: relative;
+ right: 3px;
+ }
+
+ svg {
+ position: relative;
+ right: 1px;
+ }
+ }
+
+ .stage-cell {
+
+ svg {
+ height: 18px;
+ width: 18px;
+ vertical-align: middle;
+ }
+ }
+
+ .duration,
+ .finished-at {
+ color: $table-text-gray;
+ margin: 4px 0;
+
+ .fa {
+ font-size: 12px;
+ margin-right: 4px;
+ }
+
+ svg {
+ width: 12px;
+ height: auto;
+ vertical-align: middle;
+ margin-right: 4px;
+ }
+ }
+
+ .pipeline-actions {
+ min-width: 140px;
+
+ .btn {
+ margin: 0;
+ color: $table-text-gray;
+ }
+
+ .cancel-retry-btns {
+ vertical-align: middle;
+
+ .btn:not(:first-child) {
+ margin-left: 8px;
+ }
+ }
+
+ .dropdown-toggle,
+ .dropdown-menu {
+ color: $table-text-gray;
+
+ .fa {
+ color: $table-text-gray;
+ margin-right: 6px;
+ font-size: 14px;
+ }
+ }
+
+ .btn-remove {
+ color: $white-light;
+ }
+
+ .btn-group {
+ &.open {
+ .btn-default {
+ background-color: $white-normal;
+ border-color: $border-white-normal;
+ }
+ }
+ }
+ }
+
+ .build-link {
+
+ a {
+ color: $gl-dark-link-color;
+ }
+ }
+
+ .btn-group.open .dropdown-toggle {
+ box-shadow: none;
+ }
+}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index f138a2f5387..63853335fb6 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -13,95 +13,102 @@
.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-bell,
+ .fa-spinner {
margin-right: 6px;
}
@@ -109,139 +116,117 @@
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 {
- color: $layout-link-gray;
+ svg {
+
+ path {
+ fill: $layout-link-gray;
}
- .fa-caret-down {
- margin-left: 3px;
+ use {
+ stroke: $layout-link-gray;
}
}
- form {
- margin-left: 10px;
+ .fa-caret-down {
+ margin-left: 3px;
}
+ }
- .count-buttons {
- display: inline-block;
- vertical-align: top;
- margin-top: 16px;
- }
+ .project-repo-btn-group,
+ .notification-dropdown {
+ margin-left: 10px;
+ }
- .project-clone-holder {
- display: inline-block;
- margin-top: 16px;
+ .count-buttons {
+ display: inline-block;
+ vertical-align: top;
+ }
- input {
- height: 29px;
- }
+ .project-clone-holder {
+ display: inline-block;
+
+ input {
+ height: 29px;
}
+ }
- .count-with-arrow {
- display: inline-block;
- position: relative;
- margin-left: 4px;
-
- .arrow {
- &:before {
- content: '';
- display: inline-block;
- position: absolute;
- width: 0;
- height: 0;
- border-color: transparent;
- border-style: solid;
- top: 50%;
- left: 0;
- margin-top: -6px;
- border-width: 7px 5px 7px 0;
- border-right-color: #dce0e5;
- }
+ .count-with-arrow {
+ display: inline-block;
+ position: relative;
+ margin-left: 4px;
- &:after {
- content: '';
- position: absolute;
- width: 0;
- height: 0;
- border-color: transparent;
- border-style: solid;
- top: 50%;
- left: 1px;
- margin-top: -9px;
- border-width: 10px 7px 10px 0;
- border-right-color: #fff;
- }
- }
- .count {
- @include btn-gray;
+ .arrow {
+ &:before {
+ content: '';
display: inline-block;
- background: white;
- border-radius: 2px;
- border-width: 1px;
+ position: absolute;
+ width: 0;
+ height: 0;
+ border-color: transparent;
border-style: solid;
- font-size: 13px;
- font-weight: 600;
- line-height: 13px;
- padding: $gl-vert-padding $gl-padding;
- letter-spacing: .4px;
- padding: 7px 14px;
- text-align: center;
- vertical-align: middle;
- touch-action: manipulation;
- cursor: pointer;
- background-image: none;
- white-space: nowrap;
- margin: 0 10px 0 4px;
-
- a {
- color: inherit;
- }
-
- &:hover {
- background: #fff;
- }
+ top: 50%;
+ left: 0;
+ margin-top: -6px;
+ border-width: 7px 5px 7px 0;
+ border-right-color: #dce0e5;
+ pointer-events: none;
}
- }
- }
- .project-right-buttons {
- position: absolute;
- right: 16px;
- bottom: 0;
-
- @media (max-width: $screen-md-max) {
- top: 0;
- }
-
- .access-request-button {
- position: absolute;
- right: 0;
- bottom: 61px;
-
- @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;
+ }
}
}
}
@@ -364,56 +349,91 @@ a.deploy-project-label {
}
}
-.project-import .btn {
- float: left;
- margin-bottom: 10px;
- margin-right: 10px;
+.project-import {
+ .form-group {
+ margin-bottom: 5px;
+ }
+
+ .import-buttons {
+ padding-left: 0;
+ display: -webkit-flex;
+ display: flex;
+ -webkit-flex-wrap: wrap;
+ flex-wrap: wrap;
+
+ .btn {
+ margin: 0 10px 10px 0;
+ padding: 8px;
+ }
+
+ > div {
+ padding-left: 0;
+
+ &:last-child {
+ margin-bottom: 0;
+
+ .btn {
+ margin-right: 0;
+ }
+ }
+ }
+ }
}
.project-stats {
- margin-top: $gl-padding;
- margin-bottom: 0;
- padding: 16px 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 {
- display: inline;
+ .nav > li {
+ display: inline-block;
+
+ &: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;
- margin-right: 12px;
- padding: 0 10px;
font-size: 15px;
+ line-height: 29px;
color: $notes-light-color;
- }
- li {
- display: inline;
+ &:hover,
+ &:focus {
+ color: darken($notes-light-color, 15%);
+ }
}
- a {
- float: left;
- font-size: 17px;
- }
+ li.missing {
+ border: 1px dashed $border-gray-light;
+ border-radius: $border-radius-default;
- li.missing a {
- color: #5a6069;
- border: 1px dashed #dce0e5;
+ a {
+ padding-left: 10px;
+ padding-right: 10px;
+ color: $notes-light-color;
+ display: block;
+ }
&:hover {
- background-color: #f0f2f5;
+ background-color: $gray-normal;
}
}
-
- &.row-content-block.second-block {
- margin-top: 0;
- }
}
pre.light-well {
@@ -477,6 +497,11 @@ pre.light-well {
> span {
margin-left: 10px;
}
+
+ svg {
+ position: relative;
+ top: 2px;
+ }
}
}
@@ -501,8 +526,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 {
@@ -510,19 +559,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;
}
@@ -545,15 +587,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 {
@@ -616,3 +653,15 @@ pre.light-well {
color: $gl-text-green;
}
}
+
+.project-refs-form {
+ .dropdown-menu {
+ width: 300px;
+ }
+}
+
+.compare-form-group {
+ .dropdown-menu {
+ width: 300px;
+ }
+}
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index ae524cd6bae..c9d436d72ba 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -185,7 +185,7 @@
padding-right: $gl-padding + 15px;
}
- .btn-search {
+ .btn-search, .btn-new {
width: 100%;
margin-top: 5px;
@@ -208,7 +208,7 @@
margin-top: 5px;
@media (min-width: $screen-sm-min) {
- width: 160px;
+ width: 180px;
margin-top: 0;
}
}
diff --git a/app/assets/stylesheets/pages/stat_graph.scss b/app/assets/stylesheets/pages/stat_graph.scss
index 85a0304196c..69288b31cc4 100644
--- a/app/assets/stylesheets/pages/stat_graph.scss
+++ b/app/assets/stylesheets/pages/stat_graph.scss
@@ -14,24 +14,38 @@
font-size: 10px;
}
+#contributors-master {
+ @include make-md-column(12);
+
+ svg {
+ width: 100%;
+ }
+}
+
#contributors {
.contributors-list {
margin: 0 0 10px;
list-style: none;
padding: 0;
+
+ svg {
+ width: 100%;
+ }
}
.person {
- &:nth-child(even) {
- float: right;
- }
- float: left;
+ @include make-md-column(6);
margin-top: 10px;
+
+ @media (max-width: $screen-sm-min) {
+ width: 100%;
+ }
}
.person .spark {
display: block;
background: #f3f3f3;
+ width: 100%;
}
.person .area-contributor {
diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss
index 2370d35924e..a22d4b6f6be 100644
--- a/app/assets/stylesheets/pages/status.scss
+++ b/app/assets/stylesheets/pages/status.scss
@@ -32,11 +32,23 @@
border-color: $gl-gray;
}
- &.ci-pending,
- &.ci-running {
+ &.ci-pending {
color: $gl-warning;
border-color: $gl-warning;
}
+
+ &.ci-running {
+ color: $blue-normal;
+ border-color: $blue-normal;
+ }
+
+ svg {
+ height: 13px;
+ width: 13px;
+ position: relative;
+ top: 1px;
+ margin: 0 3px;
+ }
}
.ci-status-icon-success {
@@ -45,10 +57,12 @@
.ci-status-icon-failed {
color: $gl-danger;
}
- .ci-status-icon-running,
.ci-status-icon-pending {
color: $gl-warning;
}
+ .ci-status-icon-running {
+ color: $blue-normal;
+ }
.ci-status-icon-canceled,
.ci-status-icon-disabled,
.ci-status-icon-not-found,
diff --git a/app/assets/stylesheets/pages/tags.scss b/app/assets/stylesheets/pages/tags.scss
new file mode 100644
index 00000000000..24ebd3e7cfa
--- /dev/null
+++ b/app/assets/stylesheets/pages/tags.scss
@@ -0,0 +1,7 @@
+.tag-buttons {
+ line-height: 40px;
+
+ .btn:not(.dropdown-toggle) {
+ margin-left: 10px;
+ }
+}
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
index afc00a68572..cf16d070cfe 100644
--- a/app/assets/stylesheets/pages/todos.scss
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -62,6 +62,10 @@
}
}
+ code {
+ white-space: pre-wrap;
+ }
+
pre {
border: none;
background: #f9f9f9;
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index 99c9e81ddb9..390977297fb 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -19,16 +19,15 @@
border-top: 1px solid $table-border-gray;
td, th {
- line-height: 23px;
+ line-height: 21px;
}
&:hover {
- cursor: pointer;
-
td {
background-color: $row-hover;
border-top: 1px solid $row-hover-border;
border-bottom: 1px solid $row-hover-border;
+ cursor: pointer;
}
}
@@ -101,7 +100,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/appearances_controller.rb b/app/controllers/admin/appearances_controller.rb
index 26cf74e4849..4b0ec54b3f4 100644
--- a/app/controllers/admin/appearances_controller.rb
+++ b/app/controllers/admin/appearances_controller.rb
@@ -5,6 +5,7 @@ class Admin::AppearancesController < Admin::ApplicationController
end
def preview
+ render 'preview', layout: 'devise'
end
def create
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/builds_controller.rb b/app/controllers/admin/builds_controller.rb
index 0db91eaaf2e..88f3c0e2fd4 100644
--- a/app/controllers/admin/builds_controller.rb
+++ b/app/controllers/admin/builds_controller.rb
@@ -5,8 +5,10 @@ class Admin::BuildsController < Admin::ApplicationController
@builds = @all_builds.order('created_at DESC')
@builds =
case @scope
+ when 'pending'
+ @builds.pending.reverse_order
when 'running'
- @builds.running_or_pending.reverse_order
+ @builds.running.reverse_order
when 'finished'
@builds.finished
else
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 d25619d94e0..bc65dcc33d3 100644
--- a/app/controllers/admin/runner_projects_controller.rb
+++ b/app/controllers/admin/runner_projects_controller.rb
@@ -1,15 +1,12 @@
class Admin::RunnerProjectsController < Admin::ApplicationController
before_action :project, only: [:create]
- def index
- @runner_projects = project.runner_projects.all
- @runner_project = project.runner_projects.new
- end
-
def create
@runner = Ci::Runner.find(params[:runner_project][:runner_id])
- if @runner.assign_to(@project, current_user)
+ runner_project = @runner.assign_to(@project, current_user)
+
+ if runner_project.persisted?
redirect_to admin_runner_path(@runner)
else
redirect_to admin_runner_path(@runner), alert: 'Failed adding runner to project'
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/application_controller.rb b/app/controllers/application_controller.rb
index dd1bc6f5d52..a1004d9bcea 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -36,6 +36,10 @@ class ApplicationController < ActionController::Base
render_404
end
+ rescue_from Gitlab::Access::AccessDeniedError do |exception|
+ render_403
+ end
+
def redirect_back_or_default(default: root_path, options: {})
redirect_to request.referer.present? ? :back : default, options
end
@@ -340,10 +344,6 @@ class ApplicationController < ActionController::Base
session[:skip_tfa] && session[:skip_tfa] > Time.current
end
- def browser_supports_u2f?
- browser.chrome? && browser.version.to_i >= 41 && !browser.device.mobile?
- end
-
def redirect_to_home_page_url?
# If user is not signed-in and tries to access root_path - redirect him to landing page
# Don't redirect to the default URL to prevent endless redirections
diff --git a/app/controllers/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/authenticates_with_two_factor.rb b/app/controllers/concerns/authenticates_with_two_factor.rb
index 998b8adc411..ba07cea569c 100644
--- a/app/controllers/concerns/authenticates_with_two_factor.rb
+++ b/app/controllers/concerns/authenticates_with_two_factor.rb
@@ -57,7 +57,7 @@ module AuthenticatesWithTwoFactor
# Authenticate using the response from a U2F (universal 2nd factor) device
def authenticate_with_two_factor_via_u2f(user)
- if U2fRegistration.authenticate(user, u2f_app_id, user_params[:device_response], session[:challenges])
+ if U2fRegistration.authenticate(user, u2f_app_id, user_params[:device_response], session[:challenge])
# Remove any lingering user data from login
session.delete(:otp_user_id)
session.delete(:challenges)
@@ -77,11 +77,9 @@ module AuthenticatesWithTwoFactor
if key_handles.present?
sign_requests = u2f.authentication_requests(key_handles)
- challenges = sign_requests.map(&:challenge)
- session[:challenges] = challenges
- gon.push(u2f: { challenges: challenges, app_id: u2f_app_id,
- sign_requests: sign_requests,
- browser_supports_u2f: browser_supports_u2f? })
+ session[:challenge] ||= u2f.challenge
+ gon.push(u2f: { challenge: session[:challenge], app_id: u2f_app_id,
+ sign_requests: sign_requests })
end
end
end
diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb
index dacb5679dd3..f2b8f297bc2 100644
--- a/app/controllers/concerns/creates_commit.rb
+++ b/app/controllers/concerns/creates_commit.rb
@@ -7,7 +7,8 @@ module CreatesCommit
commit_params = @commit_params.merge(
source_project: @project,
source_branch: @ref,
- target_branch: @target_branch
+ target_branch: @target_branch,
+ previous_path: @previous_path
)
result = service.new(@tree_edit_project, current_user, commit_params).execute
diff --git a/app/controllers/concerns/diff_for_path.rb b/app/controllers/concerns/diff_for_path.rb
new file mode 100644
index 00000000000..026d8b2e1e0
--- /dev/null
+++ b/app/controllers/concerns/diff_for_path.rb
@@ -0,0 +1,24 @@
+module DiffForPath
+ extend ActiveSupport::Concern
+
+ def render_diff_for_path(diffs, diff_refs, project)
+ diff_file = safe_diff_files(diffs, diff_refs: diff_refs, repository: project.repository).find do |diff|
+ diff.old_path == params[:old_path] && diff.new_path == params[:new_path]
+ end
+
+ return render_404 unless diff_file
+
+ diff_commit = commit_for_diff(diff_file)
+ blob = diff_file.blob(diff_commit)
+
+ locals = {
+ diff_file: diff_file,
+ diff_commit: diff_commit,
+ diff_refs: diff_refs,
+ blob: blob,
+ project: project
+ }
+
+ render json: { html: view_to_html_string('projects/diffs/_content', locals) }
+ end
+end
diff --git a/app/controllers/concerns/membership_actions.rb b/app/controllers/concerns/membership_actions.rb
index a24273fad0b..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,30 +20,20 @@ module MembershipActions
end
def leave
- @member = membershipable.members.find_by(user_id: current_user)
- return render_403 unless @member
+ @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)
-
- if can?(current_user, action_member_permission(:destroy, @member), @member)
- notice =
- if @member.request?
- "Your access request to the #{source_type} has been withdrawn."
- else
- "You left the \"#{@member.source.human_name}\" #{source_type}."
- end
- @member.destroy
-
- redirect_to [:dashboard, @member.real_source_type.tableize], notice: notice
- else
- if cannot_leave?
- alert = "You can not leave the \"#{@member.source.human_name}\" #{source_type}."
- alert << " Transfer or delete the #{source_type}."
- redirect_to polymorphic_url(membershipable), alert: alert
+ notice =
+ if @member.request?
+ "Your access request to the #{source_type} has been withdrawn."
else
- render_403
+ "You left the \"#{@member.source.human_name}\" #{source_type}."
end
- end
+ redirect_path = @member.request? ? @member.source : [:dashboard, @member.real_source_type.tableize]
+
+ redirect_to redirect_path, notice: notice
end
protected
@@ -51,8 +41,4 @@ module MembershipActions
def membershipable
raise NotImplementedError
end
-
- def cannot_leave?
- raise NotImplementedError
- end
end
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/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb
index 3a2db3e6eeb..19a76a5b5d8 100644
--- a/app/controllers/dashboard/todos_controller.rb
+++ b/app/controllers/dashboard/todos_controller.rb
@@ -1,6 +1,4 @@
class Dashboard::TodosController < Dashboard::ApplicationController
- include TodosHelper
-
before_action :find_todos, only: [:index, :destroy_all]
def index
@@ -13,7 +11,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
respond_to do |format|
format.html { redirect_to dashboard_todos_path, notice: 'Todo was successfully marked as done.' }
format.js { head :ok }
- format.json { render json: { count: todos_pending_count, done_count: todos_done_count } }
+ format.json { render json: todos_counts }
end
end
@@ -23,7 +21,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
respond_to do |format|
format.html { redirect_to dashboard_todos_path, notice: 'All todos were marked as done.' }
format.js { head :ok }
- format.json { render json: { count: todos_pending_count, done_count: todos_done_count } }
+ format.json { render json: todos_counts }
end
end
@@ -36,4 +34,11 @@ class Dashboard::TodosController < Dashboard::ApplicationController
def find_todos
@todos ||= TodosFinder.new(current_user, params).execute
end
+
+ def todos_counts
+ {
+ count: TodosFinder.new(current_user, state: :pending).execute.count,
+ done_count: TodosFinder.new(current_user, state: :done).execute.count
+ }
+ end
end
diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb
index d0f2e2949f0..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,11 +35,10 @@ class Groups::GroupMembersController < Groups::ApplicationController
end
def destroy
- @group_member = @group.group_members.find(params[:id])
-
- return render_403 unless can?(current_user, :destroy_group_member, @group_member)
+ @group_member = @group.members.find_by(id: params[:id]) ||
+ @group.requesters.find_by(id: params[:id])
- @group_member.destroy
+ Members::DestroyService.new(@group_member, current_user).execute
respond_to do |format|
format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' }
@@ -68,8 +68,4 @@ class Groups::GroupMembersController < Groups::ApplicationController
# MembershipActions concern
alias_method :membershipable, :group
-
- def cannot_leave?
- @group.last_owner?(current_user)
- end
end
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/help_controller.rb b/app/controllers/help_controller.rb
index 9b5c43b17e2..d3dd98c8a4e 100644
--- a/app/controllers/help_controller.rb
+++ b/app/controllers/help_controller.rb
@@ -12,13 +12,12 @@ class HelpController < ApplicationController
end
def show
- @category = clean_path_info(path_params[:category])
- @file = path_params[:file]
+ @path = clean_path_info(path_params[:path])
respond_to do |format|
format.any(:markdown, :md, :html) do
# Note: We are purposefully NOT using `Rails.root.join`
- path = File.join(Rails.root, 'doc', @category, "#{@file}.md")
+ path = File.join(Rails.root, 'doc', "#{@path}.md")
if File.exist?(path)
@markdown = File.read(path)
@@ -33,7 +32,7 @@ class HelpController < ApplicationController
# Allow access to images in the doc folder
format.any(:png, :gif, :jpeg) do
# Note: We are purposefully NOT using `Rails.root.join`
- path = File.join(Rails.root, 'doc', @category, "#{@file}.#{params[:format]}")
+ path = File.join(Rails.root, 'doc', "#{@path}.#{params[:format]}")
if File.exist?(path)
send_file(path, disposition: 'inline')
@@ -57,8 +56,7 @@ class HelpController < ApplicationController
private
def path_params
- params.require(:category)
- params.require(:file)
+ params.require(:path)
params
end
diff --git a/app/controllers/import/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 f99aa490d3e..30df1fb2fec 100644
--- a/app/controllers/import/gitlab_projects_controller.rb
+++ b/app/controllers/import/gitlab_projects_controller.rb
@@ -12,9 +12,13 @@ class Import::GitlabProjectsController < Import::BaseController
return redirect_back_or_default(options: { alert: "You need to upload a GitLab project export archive." })
end
+ imported_file = project_params[:file].path + "-import"
+
+ FileUtils.copy_entry(project_params[:file].path, imported_file)
+
@project = Gitlab::ImportExport::ProjectCreator.new(project_params[:namespace_id],
current_user,
- File.expand_path(project_params[:file].path),
+ File.expand_path(imported_file),
project_params[:path]).execute
if @project.saved?
@@ -23,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/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index f35d631df0c..f54c79c2e37 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -107,7 +107,11 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
# Only allow properly saved users to login.
if @user.persisted? && @user.valid?
log_audit_event(@user, with: oauth['provider'])
- sign_in_and_redirect(@user)
+ if @user.two_factor_enabled?
+ prompt_for_two_factor(@user)
+ else
+ sign_in_and_redirect(@user)
+ end
else
error_message = @user.errors.full_messages.to_sentence
diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb
index 6a358fdcc05..e37e9e136db 100644
--- a/app/controllers/profiles/two_factor_auths_controller.rb
+++ b/app/controllers/profiles/two_factor_auths_controller.rb
@@ -100,7 +100,6 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
gon.push(u2f: { challenges: session[:challenges], app_id: u2f_app_id,
register_requests: registration_requests,
- sign_requests: sign_requests,
- browser_supports_u2f: browser_supports_u2f? })
+ sign_requests: sign_requests })
end
end
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index 776ba92c9ab..996909a28c6 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -74,7 +74,7 @@ class Projects::ApplicationController < ApplicationController
end
def require_branch_head
- unless @repository.branch_names.include?(@ref)
+ unless @repository.branch_exists?(@ref)
redirect_to(
namespace_project_tree_path(@project.namespace, @project, @ref),
notice: "This action is not allowed unless you are on a branch"
diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb
index f11c8321464..7241949393b 100644
--- a/app/controllers/projects/artifacts_controller.rb
+++ b/app/controllers/projects/artifacts_controller.rb
@@ -23,10 +23,9 @@ class Projects::ArtifactsController < Projects::ApplicationController
entry = build.artifacts_metadata_entry(params[:path])
if entry.exists?
- render json: { archive: build.artifacts_file.path,
- entry: Base64.encode64(entry.path) }
+ send_artifacts_entry(build, entry)
else
- render json: {}, status: 404
+ render_404
end
end
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index cd8b2911674..eda3727a28d 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -16,6 +16,7 @@ class Projects::BlobController < Projects::ApplicationController
before_action :from_merge_request, only: [:edit, :update]
before_action :require_branch_head, only: [:edit, :update]
before_action :editor_variables, except: [:show, :preview, :diff]
+ before_action :validate_diff_params, only: :diff
def new
commit unless @repository.empty?
@@ -37,6 +38,12 @@ class Projects::BlobController < Projects::ApplicationController
end
def update
+ if params[:file_path].present?
+ @previous_path = @path
+ @path = params[:file_path]
+ @commit_params[:file_path] = @path
+ end
+
after_edit_path =
if from_merge_request && @target_branch == @ref
diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) +
@@ -56,7 +63,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
@@ -146,4 +153,10 @@ class Projects::BlobController < Projects::ApplicationController
file_content_encoding: params[:encoding]
}
end
+
+ def validate_diff_params
+ if [:since, :to, :offset].any? { |key| params[key].blank? }
+ render nothing: true
+ end
+ end
end
diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb
index ef3051d7519..553b62741a5 100644
--- a/app/controllers/projects/builds_controller.rb
+++ b/app/controllers/projects/builds_controller.rb
@@ -1,6 +1,6 @@
class Projects::BuildsController < Projects::ApplicationController
before_action :build, except: [:index, :cancel_all]
- before_action :authorize_read_build!, except: [:cancel, :cancel_all, :retry]
+ before_action :authorize_read_build!, except: [:cancel, :cancel_all, :retry, :play]
before_action :authorize_update_build!, except: [:index, :show, :status, :raw]
layout 'project'
@@ -10,8 +10,10 @@ class Projects::BuildsController < Projects::ApplicationController
@builds = @all_builds.order('created_at DESC')
@builds =
case @scope
+ when 'pending'
+ @builds.pending.reverse_order
when 'running'
- @builds.running_or_pending.reverse_order
+ @builds.running.reverse_order
when 'finished'
@builds.finished
else
@@ -47,14 +49,19 @@ class Projects::BuildsController < Projects::ApplicationController
end
def retry
- unless @build.retryable?
- return render_404
- end
+ return render_404 unless @build.retryable?
build = Ci::Build.retry(@build, current_user)
redirect_to build_path(build)
end
+ def play
+ return render_404 unless @build.playable?
+
+ build = @build.play(current_user)
+ redirect_to build_path(build)
+ end
+
def cancel
@build.cancel
redirect_to build_path(@build)
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 6751737d15e..727e84b40a1 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -3,6 +3,7 @@
# Not to be confused with CommitsController, plural.
class Projects::CommitController < Projects::ApplicationController
include CreatesCommit
+ include DiffForPath
include DiffHelper
# Authorize
@@ -11,22 +12,14 @@ class Projects::CommitController < Projects::ApplicationController
before_action :authorize_update_build!, only: [:cancel_builds, :retry_builds]
before_action :authorize_read_commit_status!, only: [:builds]
before_action :commit
- before_action :define_show_vars, only: [:show, :builds]
+ before_action :define_commit_vars, only: [:show, :diff_for_path, :builds]
+ before_action :define_status_vars, only: [:show, :builds]
+ before_action :define_note_vars, only: [:show, :diff_for_path]
before_action :authorize_edit_tree!, only: [:revert, :cherry_pick]
def show
apply_diff_view_cookie!
- @grouped_diff_notes = commit.notes.grouped_diff_notes
-
- @note = @project.build_commit_note(commit)
- @notes = commit.notes.non_diff_notes.fresh
- @noteable = @commit
- @comments_target = {
- noteable_type: 'Commit',
- commit_id: @commit.id
- }
-
respond_to do |format|
format.html
format.diff { render text: @commit.to_diff }
@@ -34,6 +27,10 @@ class Projects::CommitController < Projects::ApplicationController
end
end
+ def diff_for_path
+ render_diff_for_path(@diffs, @commit.diff_refs, @project)
+ end
+
def builds
end
@@ -107,16 +104,36 @@ class Projects::CommitController < Projects::ApplicationController
@ci_builds ||= Ci::Build.where(pipeline: pipelines)
end
- def define_show_vars
+ def define_commit_vars
return git_not_found! unless commit
opts = diff_options
opts[:ignore_whitespace_change] = true if params[:format] == 'diff'
@diffs = commit.diffs(opts)
- @diff_refs = [commit.parent || commit, commit]
@notes_count = commit.notes.count
+ end
+
+ def define_note_vars
+ @grouped_diff_notes = commit.notes.grouped_diff_notes
+ @notes = commit.notes.non_diff_notes.fresh
+
+ Banzai::NoteRenderer.render(
+ @grouped_diff_notes.values.flatten + @notes,
+ @project,
+ current_user,
+ )
+
+ @note = @project.build_commit_note(commit)
+
+ @noteable = @commit
+ @comments_target = {
+ noteable_type: 'Commit',
+ commit_id: @commit.id
+ }
+ end
+ def define_status_vars
@statuses = CommitStatus.where(pipeline: pipelines)
@builds = Ci::Build.where(pipeline: pipelines)
end
diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb
index af0b69a2442..5f3ee71444d 100644
--- a/app/controllers/projects/compare_controller.rb
+++ b/app/controllers/projects/compare_controller.rb
@@ -1,30 +1,26 @@
require 'addressable/uri'
class Projects::CompareController < Projects::ApplicationController
+ include DiffForPath
include DiffHelper
# Authorize
before_action :require_non_empty_project
before_action :authorize_download_code!
- before_action :assign_ref_vars, only: [:index, :show]
+ before_action :define_ref_vars, only: [:index, :show, :diff_for_path]
+ before_action :define_diff_vars, only: [:show, :diff_for_path]
before_action :merge_request, only: [:index, :show]
def index
end
def show
- compare = CompareService.new.
- execute(@project, @head_ref, @project, @base_ref, diff_options)
+ end
- if compare
- @commits = Commit.decorate(compare.commits, @project)
- @commit = @project.commit(@head_ref)
- @base_commit = @project.merge_base_commit(@base_ref, @head_ref)
- @diffs = compare.diffs(diff_options)
- @diff_refs = [@base_commit, @commit]
- @diff_notes_disabled = true
- @grouped_diff_notes = {}
- end
+ def diff_for_path
+ return render_404 unless @compare
+
+ render_diff_for_path(@diffs, @diff_refs, @project)
end
def create
@@ -34,13 +30,35 @@ class Projects::CompareController < Projects::ApplicationController
private
- def assign_ref_vars
- @base_ref = Addressable::URI.unescape(params[:from])
+ def define_ref_vars
+ @start_ref = Addressable::URI.unescape(params[:from])
@ref = @head_ref = Addressable::URI.unescape(params[:to])
end
+ def define_diff_vars
+ @compare = CompareService.new.execute(@project, @head_ref, @project, @start_ref)
+
+ if @compare
+ @commits = Commit.decorate(@compare.commits, @project)
+
+ @start_commit = @project.commit(@start_ref)
+ @commit = @project.commit(@head_ref)
+ @base_commit = @project.merge_base_commit(@start_ref, @head_ref)
+
+ @diffs = @compare.diffs(diff_options)
+ @diff_refs = Gitlab::Diff::DiffRefs.new(
+ base_sha: @base_commit.try(:sha),
+ start_sha: @start_commit.try(:sha),
+ head_sha: @commit.try(:sha)
+ )
+
+ @diff_notes_disabled = true
+ @grouped_diff_notes = {}
+ end
+ end
+
def merge_request
@merge_request ||= @project.merge_requests.opened.
- find_by(source_project: @project, source_branch: @head_ref, target_branch: @base_ref)
+ find_by(source_project: @project, source_branch: @head_ref, target_branch: @start_ref)
end
end
diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb
index 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 4e2d3bebb2e..fa663c9bda4 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -62,8 +62,12 @@ class Projects::IssuesController < Projects::ApplicationController
end
def show
+ raw_notes = @issue.notes_with_associations.fresh
+
+ @notes = Banzai::NoteRenderer.
+ render(raw_notes, @project, current_user, @path, @project_wiki, @ref)
+
@note = @project.notes.new(noteable: @issue)
- @notes = @issue.notes.with_associations.fresh
@noteable = @issue
respond_to do |format|
@@ -72,7 +76,6 @@ class Projects::IssuesController < Projects::ApplicationController
render json: @issue.to_json(include: [:milestone, :labels])
end
end
-
end
def create
@@ -111,6 +114,7 @@ class Projects::IssuesController < Projects::ApplicationController
render :edit
end
end
+
format.json do
render json: @issue.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } })
end
@@ -222,6 +226,7 @@ class Projects::IssuesController < Projects::ApplicationController
:assignee_id,
:milestone_id,
:state_event,
+ :subscription_event,
label_ids: [],
add_label_ids: [],
remove_label_ids: []
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 851822d805a..df659bb8c3b 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -1,5 +1,6 @@
class Projects::MergeRequestsController < Projects::ApplicationController
include ToggleSubscriptionAction
+ include DiffForPath
include DiffHelper
include IssuableActions
include ToggleAwardEmoji
@@ -9,10 +10,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
:edit, :update, :show, :diffs, :commits, :builds, :merge, :merge_check,
:ci_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip
]
- before_action :closes_issues, only: [:edit, :update, :show, :diffs, :commits, :builds]
before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds]
before_action :define_show_vars, only: [:show, :diffs, :commits, :builds]
before_action :define_widget_vars, only: [:merge, :cancel_merge_when_build_succeeds, :merge_check]
+ before_action :define_commit_vars, only: [:diffs]
+ before_action :define_diff_comment_vars, only: [:diffs]
before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds]
# Allow read any merge_request
@@ -53,13 +55,19 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def show
- @note_counts = Note.where(commit_id: @merge_request.commits.map(&:id)).
- group(:commit_id).count
-
respond_to do |format|
- format.html
- format.json { render json: @merge_request }
- format.patch { render text: @merge_request.to_patch }
+ format.html { define_discussion_vars }
+
+ 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,43 +79,65 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def diffs
apply_diff_view_cookie!
- @commit = @merge_request.last_commit
- @base_commit = @merge_request.diff_base_commit
-
- # MRs created before 8.4 don't have a diff_base_commit,
- # but we need it for the "View file @ ..." link by deleted files
- @base_commit ||= @merge_request.first_commit.parent || @merge_request.first_commit
-
- @comments_target = {
- noteable_type: 'MergeRequest',
- noteable_id: @merge_request.id
- }
-
- @grouped_diff_notes = @merge_request.notes.grouped_diff_notes
+ @merge_request_diff = @merge_request.merge_request_diff
respond_to do |format|
- format.html
+ format.html { define_discussion_vars }
format.json { render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") } }
end
end
+ # With an ID param, loads the MR at that ID. Otherwise, accepts the same params as #new
+ # and uses that (unsaved) MR.
+ #
+ def diff_for_path
+ if params[:id]
+ merge_request
+ define_diff_comment_vars
+ else
+ build_merge_request
+ @diff_notes_disabled = true
+ @grouped_diff_notes = {}
+ end
+
+ define_commit_vars
+ diffs = @merge_request.diffs(diff_options)
+
+ render_diff_for_path(diffs, @merge_request.diff_refs, @merge_request.project)
+ end
+
def commits
respond_to do |format|
- format.html { render 'show' }
- format.json { render json: { html: view_to_html_string('projects/merge_requests/show/_commits') } }
+ format.html do
+ define_discussion_vars
+
+ render 'show'
+ end
+ format.json do
+ # Get commits from repository
+ # or from cache if already merged
+ @commits = @merge_request.commits
+ @note_counts = Note.where(commit_id: @commits.map(&:id)).
+ group(:commit_id).count
+
+ render json: { html: view_to_html_string('projects/merge_requests/show/_commits') }
+ end
end
end
def builds
respond_to do |format|
- format.html { render 'show' }
+ format.html do
+ define_discussion_vars
+
+ render 'show'
+ end
format.json { render json: { html: view_to_html_string('projects/merge_requests/show/_builds') } }
end
end
def new
- params[:merge_request] ||= ActionController::Parameters.new(source_project: @project)
- @merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params).execute
+ build_merge_request
@noteable = @merge_request
@target_branches = if @merge_request.target_project
@@ -119,7 +149,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@target_project = merge_request.target_project
@source_project = merge_request.source_project
@commits = @merge_request.compare_commits.reverse
- @commit = @merge_request.last_commit
+ @commit = @merge_request.diff_head_commit
@base_commit = @merge_request.diff_base_commit
@diffs = @merge_request.compare.diffs(diff_options) if @merge_request.compare
@diff_notes_disabled = true
@@ -190,12 +220,14 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def merge
return access_denied! unless @merge_request.can_be_merged_by?(current_user)
- unless @merge_request.mergeable?
+ # Disable the CI check if merge_when_build_succeeds is enabled since we have
+ # to wait until CI completes to know
+ unless @merge_request.mergeable?(skip_ci_check: merge_when_build_succeeds_active?)
@status = :failed
return
end
- if params[:sha] != @merge_request.source_sha
+ if params[:sha] != @merge_request.diff_head_sha
@status = :sha_mismatch
return
end
@@ -204,8 +236,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request.update(merge_error: nil)
- if params[:merge_when_build_succeeds].present?
- if @merge_request.pipeline && @merge_request.pipeline.active?
+ if params[:merge_when_build_succeeds].present?
+ unless @merge_request.pipeline
+ @status = :failed
+ return
+ end
+
+ if @merge_request.pipeline.active?
MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params)
.execute(@merge_request)
@status = :merge_when_build_succeeds
@@ -252,16 +289,16 @@ class Projects::MergeRequestsController < Projects::ApplicationController
status ||= "preparing"
else
ci_service = @merge_request.source_project.ci_service
- status = ci_service.commit_status(merge_request.last_commit.sha, merge_request.source_branch) if ci_service
+ status = ci_service.commit_status(merge_request.diff_head_sha, merge_request.source_branch) if ci_service
if ci_service.respond_to?(:commit_coverage)
- coverage = ci_service.commit_coverage(merge_request.last_commit.sha, merge_request.source_branch)
+ coverage = ci_service.commit_coverage(merge_request.diff_head_sha, merge_request.source_branch)
end
end
response = {
title: merge_request.title,
- sha: merge_request.last_commit_short_sha,
+ sha: merge_request.diff_head_commit.short_id,
status: status,
coverage: coverage
}
@@ -286,10 +323,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
alias_method :issuable, :merge_request
alias_method :awardable, :merge_request
- def closes_issues
- @closes_issues ||= @merge_request.closes_issues
- end
-
def authorize_update_merge_request!
return render_404 unless can?(current_user, :update_merge_request, @merge_request)
end
@@ -318,17 +351,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def define_show_vars
- # Build a note object for comment form
- @note = @project.notes.new(noteable: @merge_request)
- @notes = @merge_request.mr_and_commit_notes.inc_author.fresh
- @discussions = @notes.discussions
@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
+ @commits_count = @merge_request.commits.count
@pipeline = @merge_request.pipeline
@statuses = @pipeline.statuses if @pipeline
@@ -339,10 +363,55 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
end
+ # Discussion tab data is rendered on html responses of actions
+ # :show, :diff, :commits, :builds. but not when request the data through AJAX
+ def define_discussion_vars
+ # Build a note object for comment form
+ @note = @project.notes.new(noteable: @noteable)
+
+ @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,
+ current_user,
+ @path,
+ @project_wiki,
+ @ref
+ )
+ end
+
def define_widget_vars
@pipeline = @merge_request.pipeline
@pipelines = [@pipeline].compact
- closes_issues
+ end
+
+ def define_commit_vars
+ @commit = @merge_request.diff_head_commit
+ @base_commit = @merge_request.diff_base_commit || @merge_request.likely_diff_base_commit
+ end
+
+ def define_diff_comment_vars
+ @comments_target = {
+ noteable_type: 'MergeRequest',
+ noteable_id: @merge_request.id
+ }
+
+ @use_legacy_diff_notes = !@merge_request.support_new_diff_notes?
+ @grouped_diff_notes = @merge_request.notes.grouped_diff_notes
+
+ Banzai::NoteRenderer.render(
+ @grouped_diff_notes.values.flatten,
+ @project,
+ current_user,
+ @path,
+ @project_wiki,
+ @ref
+ )
end
def invalid_mr
@@ -368,4 +437,14 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def ensure_ref_fetched
@merge_request.ensure_ref_fetched
end
+
+ def merge_when_build_succeeds_active?
+ params[:merge_when_build_succeeds].present? &&
+ @merge_request.pipeline && @merge_request.pipeline.active?
+ end
+
+ def build_merge_request
+ params[:merge_request] ||= ActionController::Parameters.new(source_project: @project)
+ @merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params).execute
+ end
end
diff --git a/app/controllers/projects/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 836f79ff080..3eacdbbd067 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -24,6 +24,10 @@ class Projects::NotesController < Projects::ApplicationController
def create
@note = Notes::CreateService.new(project, current_user, note_params).execute
+ if @note.is_a?(Note)
+ Banzai::NoteRenderer.render([@note], @project, current_user)
+ end
+
respond_to do |format|
format.json { render json: note_json(@note) }
format.html { redirect_back_or_default }
@@ -33,6 +37,10 @@ class Projects::NotesController < Projects::ApplicationController
def update
@note = Notes::UpdateService.new(project, current_user, note_params).execute(note)
+ if @note.is_a?(Note)
+ Banzai::NoteRenderer.render([@note], @project, current_user)
+ end
+
respond_to do |format|
format.json { render json: note_json(@note) }
format.html { redirect_back_or_default }
@@ -118,7 +126,9 @@ class Projects::NotesController < Projects::ApplicationController
name: note.name
}
elsif note.valid?
- {
+ Banzai::NoteRenderer.render([note], @project, current_user)
+
+ attrs = {
valid: true,
id: note.id,
discussion_id: note.discussion_id,
@@ -128,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,
@@ -144,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/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index 127bd1a4318..487963fdcd7 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -54,6 +54,6 @@ class Projects::PipelinesController < Projects::ApplicationController
end
def commit
- @commit ||= @pipeline.commit_data
+ @commit ||= @pipeline.commit
end
end
diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
index 35d067cd029..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,11 +50,10 @@ class Projects::ProjectMembersController < Projects::ApplicationController
end
def destroy
- @project_member = @project.project_members.find(params[:id])
-
- return render_403 unless can?(current_user, :destroy_project_member, @project_member)
+ @project_member = @project.members.find_by(id: params[:id]) ||
+ @project.requesters.find_by(id: params[:id])
- @project_member.destroy
+ Members::DestroyService.new(@project_member, current_user).execute
respond_to do |format|
format.html do
@@ -98,8 +99,4 @@ class Projects::ProjectMembersController < Projects::ApplicationController
# MembershipActions concern
alias_method :membershipable, :project
-
- def cannot_leave?
- current_user == @project.owner
- end
end
diff --git a/app/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb
index efa7bf14d0f..10dca47fded 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,7 +45,11 @@ 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)
+ params.require(:protected_branch).permit(:name, :developers_can_push, :developers_can_merge)
end
end
diff --git a/app/controllers/projects/runner_projects_controller.rb b/app/controllers/projects/runner_projects_controller.rb
index bedeb4a295c..8267b14941d 100644
--- a/app/controllers/projects/runner_projects_controller.rb
+++ b/app/controllers/projects/runner_projects_controller.rb
@@ -6,11 +6,12 @@ class Projects::RunnerProjectsController < Projects::ApplicationController
def create
@runner = Ci::Runner.find(params[:runner_project][:runner_id])
- 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)
- if @runner.assign_to(project, current_user)
+ if runner_project.persisted?
redirect_to path
else
redirect_to path, alert: 'Failed adding runner to project'
diff --git a/app/controllers/projects/runners_controller.rb b/app/controllers/projects/runners_controller.rb
index 0b4fa572501..53c36635efe 100644
--- a/app/controllers/projects/runners_controller.rb
+++ b/app/controllers/projects/runners_controller.rb
@@ -5,10 +5,9 @@ class Projects::RunnersController < Projects::ApplicationController
layout 'project_settings'
def index
- @runners = project.runners.ordered
- @specific_runners = current_user.ci_authorized_runners.
- where.not(id: project.runners).
- ordered.page(params[:page]).per(20)
+ @project_runners = project.runners.ordered
+ @assignable_runners = current_user.ci_authorized_runners.
+ assignable_for(project).ordered.page(params[:page]).per(20)
@shared_runners = Ci::Runner.shared.active
@shared_runners_count = @shared_runners.count(:all)
end
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index 739681f4085..1b91882048e 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -45,8 +45,9 @@ class Projects::ServicesController < Projects::ApplicationController
end
def test
- data = Gitlab::PushDataBuilder.build_sample(project, current_user)
+ data = @service.test_data(project, current_user)
outcome = @service.test(data)
+
if outcome[:success]
message = { notice: 'We sent a request to the provided URL' }
else
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/todos_controller.rb b/app/controllers/projects/todos_controller.rb
index 23868d986e9..5685d0f4e7c 100644
--- a/app/controllers/projects/todos_controller.rb
+++ b/app/controllers/projects/todos_controller.rb
@@ -5,7 +5,7 @@ class Projects::TodosController < Projects::ApplicationController
todo = TodoService.new.mark_todo(issuable, current_user)
render json: {
- count: current_user.todos_pending_count,
+ count: TodosFinder.new(current_user, state: :pending).execute.count,
delete_path: dashboard_todo_path(todo)
}
end
diff --git a/app/controllers/projects/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 8044c637825..4e5bcff9cf8 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -1,10 +1,11 @@
class ProjectsController < Projects::ApplicationController
include ExtractsPath
- before_action :authenticate_user!, except: [:show, :activity]
+ 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: [:repo_exists?, :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
@@ -251,6 +252,24 @@ class ProjectsController < Projects::ApplicationController
}
end
+ def refs
+ options = {
+ 'Branches' => @repository.branch_names,
+ }
+
+ unless @repository.tag_count.zero?
+ options['Tags'] = VersionSorter.rsort(@repository.tag_names)
+ end
+
+ # If reference is commit id - we should add it to branch/tag selectbox
+ ref = Addressable::URI.unescape(params[:ref])
+ if ref && options.flatten(2).exclude?(ref) && ref =~ /\A[0-9a-zA-Z]{6,52}\z/
+ options['Commits'] = [ref]
+ end
+
+ render json: options.to_json
+ end
+
private
def determine_layout
@@ -285,8 +304,18 @@ class ProjectsController < Projects::ApplicationController
project.repository_exists? && !project.empty_repo?
end
- # Override get_id from ExtractsPath, which returns the branch and file path
+ 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.
+ def extract_ref(_id)
+ [get_id, '']
+ end
+
+ # Override get_id from ExtractsPath in this case is just the root of the default branch.
def get_id
project.repository.root_ref
end
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..ff866c2faa5 100644
--- a/app/finders/todos_finder.rb
+++ b/app/finders/todos_finder.rb
@@ -8,7 +8,7 @@
# action_id: integer
# author_id: integer
# project_id; integer
-# state: 'pending' or 'done'
+# state: 'pending' (default) or 'done'
# type: 'Issue' or 'MergeRequest'
#
@@ -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)
@@ -36,13 +37,25 @@ class TodosFinder
private
def action_id?
- action_id.present? && [Todo::ASSIGNED, Todo::MENTIONED, Todo::BUILD_FAILED, Todo::MARKED].include?(action_id.to_i)
+ action_id.present? && Todo::ACTION_NAMES.has_key?(action_id.to_i)
end
def action_id
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..e12a1052988 100644
--- a/app/helpers/appearances_helper.rb
+++ b/app/helpers/appearances_helper.rb
@@ -31,7 +31,7 @@ module AppearancesHelper
end
end
- def navbar_icon(icon_name)
- render "shared/icons/#{icon_name}.svg"
+ def custom_icon(icon_name, size: 16)
+ render "shared/icons/#{icon_name}.svg", size: size
end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 439b015b3b8..03495cf5ec4 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -101,22 +101,6 @@ module ApplicationHelper
'Never'
end
- def grouped_options_refs
- repository = @project.repository
-
- options = [
- ['Branches', repository.branch_names],
- ['Tags', VersionSorter.rsort(repository.tag_names)]
- ]
-
- # If reference is commit id - we should add it to branch/tag selectbox
- if @ref && !options.flatten.include?(@ref) && @ref =~ /\A[0-9a-zA-Z]{6,52}\z/
- options << ['Commit', [@ref]]
- end
-
- grouped_options_for_select(options, @ref || @project.default_branch)
- end
-
# Define whenever show last push event
# with suggestion to create MR
def show_last_push_widget?(event)
@@ -132,7 +116,7 @@ module ApplicationHelper
return false if project.merge_requests.where(source_branch: event.branch_name).opened.any?
# Skip if user removed branch right after that
- return false unless project.repository.branch_names.include?(event.branch_name)
+ return false unless project.repository.branch_exists?(event.branch_name)
true
end
@@ -213,7 +197,7 @@ module ApplicationHelper
def render_markup(file_name, file_content)
if gitlab_markdown?(file_name)
- Haml::Helpers.preserve(markdown(file_content))
+ Hamlit::RailsHelpers.preserve(markdown(file_content))
elsif asciidoc?(file_name)
asciidoc(file_content)
elsif plain?(file_name)
@@ -322,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 5b54b34070c..abe115d8c68 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -1,10 +1,7 @@
module BlobHelper
- def highlighter(blob_name, blob_content, nowrap: false)
- Gitlab::Highlight.new(blob_name, blob_content, nowrap: nowrap)
- 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, plain: false)
+ highlighted = Gitlab::Highlight.highlight(blob_name, blob_content, plain: plain, repository: repository)
+ raw %(<pre class="code highlight"><code>#{highlighted}</code></pre>)
end
def no_highlight_files
@@ -29,7 +26,7 @@ module BlobHelper
if !on_top_of_branch?(project, ref)
button_tag "Edit", class: "btn disabled has-tooltip btn-file-option", title: "You can only edit files when you are on a branch", data: { container: 'body' }
elsif can_edit_blob?(blob, project, ref)
- link_to "Edit", edit_path, class: 'btn btn-file-option'
+ link_to "Edit", edit_path, class: 'btn btn-sm'
elsif can?(current_user, :fork_project, project)
continue_params = {
to: edit_path,
@@ -186,12 +183,16 @@ module BlobHelper
end
def gitignore_names
- return @gitignore_names if defined?(@gitignore_names)
+ @gitignore_names ||=
+ Gitlab::Template::Gitignore.categories.keys.map do |k|
+ [k, Gitlab::Template::Gitignore.by_category(k).map { |t| { name: t.name } }]
+ end.to_h
+ end
- @gitignore_names = {
- Global: Gitlab::Gitignore.global.map { |gitignore| { name: gitignore.name } },
- # Note that the key here doesn't cover it really
- Languages: Gitlab::Gitignore.languages_frameworks.map{ |gitignore| { name: gitignore.name } }
- }
+ def gitlab_ci_ymls
+ @gitlab_ci_ymls ||=
+ Gitlab::Template::GitlabCiYml.categories.keys.map do |k|
+ [k, Gitlab::Template::GitlabCiYml.by_category(k).map { |t| { name: t.name } }]
+ end.to_h
end
end
diff --git a/app/helpers/branches_helper.rb b/app/helpers/branches_helper.rb
index 3ee3fc74f0c..bfd23aa4e04 100644
--- a/app/helpers/branches_helper.rb
+++ b/app/helpers/branches_helper.rb
@@ -10,9 +10,9 @@ module BranchesHelper
end
def can_push_branch?(project, branch_name)
- return false unless project.repository.branch_names.include?(branch_name)
+ return false unless project.repository.branch_exists?(branch_name)
- ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(branch_name)
+ ::Gitlab::UserAccess.new(current_user, project: project).can_push_to_branch?(branch_name)
end
def project_branches
diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb
index 9051a493b9b..b478580978b 100644
--- a/app/helpers/button_helper.rb
+++ b/app/helpers/button_helper.rb
@@ -15,58 +15,42 @@ module ButtonHelper
#
# See http://clipboardjs.com/#usage
def clipboard_button(data = {})
+ data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data)
content_tag :button,
icon('clipboard'),
class: "btn btn-clipboard",
data: data,
- type: :button
+ type: :button,
+ title: "Copy to Clipboard"
end
- # Output a "Copy to Clipboard" button with a custom CSS class
- #
- # data - Data attributes passed to `content_tag`
- # css_class - Class passed to the `content_tag`
- #
- # Examples:
- #
- # # Define the target element
- # clipboard_button_with_class({clipboard_target: "div#foo"}, css_class: "btn-clipboard")
- # # => "<button class='btn btn-clipboard' data-clipboard-target='div#foo'>...</button>"
- def clipboard_button_with_class(data = {}, css_class: 'btn-clipboard')
- content_tag :button,
- icon('clipboard'),
- class: "btn #{css_class}",
- data: data,
- type: :button
- end
-
- def http_clone_button(project)
+ def http_clone_button(project, placement = 'right', append_link: true)
klass = 'http-selector'
klass << ' has-tooltip' if current_user.try(:require_password?)
protocol = gitlab_config.protocol.upcase
- content_tag :a, protocol,
+ content_tag (append_link ? :a : :span), protocol,
class: klass,
- href: project.http_url_to_repo,
+ href: (project.http_url_to_repo if append_link),
data: {
html: true,
- placement: 'right',
+ placement: placement,
container: 'body',
title: "Set a password on your account<br>to pull or push via #{protocol}"
}
end
- def ssh_clone_button(project)
+ def ssh_clone_button(project, placement = 'right', append_link: true)
klass = 'ssh-selector'
klass << ' has-tooltip' if current_user.try(:require_ssh_key?)
- content_tag :a, 'SSH',
+ content_tag (append_link ? :a : :span), 'SSH',
class: klass,
- href: project.ssh_url_to_repo,
+ href: (project.ssh_url_to_repo if append_link),
data: {
html: true,
- placement: 'right',
+ placement: placement,
container: 'body',
title: 'Add an SSH key to your profile<br>to pull or push via SSH.'
}
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index 8e4ae1e6aec..59a8365d60b 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -26,16 +26,20 @@ module CiStatusHelper
icon_name =
case status
when 'success'
- 'check'
+ 'icon_status_success'
+ when 'success_with_warnings'
+ 'icon_status_warning'
when 'failed'
- 'close'
- when 'running', 'pending'
- 'clock-o'
+ 'icon_status_failed'
+ when 'pending'
+ 'icon_status_pending'
+ when 'running'
+ 'icon_status_running'
else
- 'circle'
+ 'icon_status_cancel'
end
- icon(icon_name + ' fw')
+ custom_icon(icon_name)
end
def render_commit_status(commit, tooltip_placement: 'auto left', cssclass: '')
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index e22dce59d0f..75b029365f9 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -8,6 +8,10 @@ module DiffHelper
[marked_old_line, marked_new_line]
end
+ def expand_all_diffs?
+ params[:expand_all_diffs].present?
+ end
+
def diff_view
diff_views = %w(inline parallel)
@@ -18,28 +22,23 @@ module DiffHelper
end
end
- def diff_hard_limit_enabled?
- params[:force_show_diff].present?
- end
-
def diff_options
- options = { ignore_whitespace_change: hide_whitespace? }
- if diff_hard_limit_enabled?
- options.merge!(Commit.max_diff_options)
+ options = { ignore_whitespace_change: hide_whitespace?, no_collapse: expand_all_diffs? }
+
+ if action_name == 'diff_for_path'
+ options[:no_collapse] = true
+ options[:paths] = params.values_at(:old_path, :new_path)
end
- options
- end
- def safe_diff_files(diffs, diff_refs)
- diffs.decorate! { |diff| Gitlab::Diff::File.new(diff, diff_refs) }
+ Commit.max_diff_options.merge(options)
end
- def generate_line_code(file_path, line)
- Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos)
+ def safe_diff_files(diffs, diff_refs: nil, repository: nil)
+ diffs.decorate! { |diff| Gitlab::Diff::File.new(diff, diff_refs: diff_refs, repository: repository) }
end
def unfold_bottom_class(bottom)
- bottom ? 'js-unfold-bottom' : ''
+ bottom ? 'js-unfold js-unfold-bottom' : ''
end
def unfold_class(unfold)
@@ -93,6 +92,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 +101,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/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
index 067a00660aa..1a259656f31 100644
--- a/app/helpers/gitlab_markdown_helper.rb
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -50,8 +50,6 @@ module GitlabMarkdownHelper
context[:project] ||= @project
- text = Banzai.pre_process(text, context)
-
html = Banzai.render(text, context)
context.merge!(
@@ -185,4 +183,17 @@ module GitlabMarkdownHelper
''
end
end
+
+ def markdown_toolbar_button(options = {})
+ data = options[:data].merge({ container: "body" })
+ content_tag :button,
+ type: "button",
+ class: "toolbar-btn js-md has-tooltip hidden-xs",
+ tabindex: -1,
+ data: data,
+ title: options[:title],
+ aria: { label: options[:title] } do
+ icon(options[:icon])
+ end
+ end
end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 8231ce49fac..47d174361db 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
@@ -62,7 +61,7 @@ module IssuablesHelper
output = content_tag :strong, "#{text} #{issuable.to_reference}", class: "identifier"
output << " opened #{time_ago_with_tooltip(issuable.created_at)} by ".html_safe
output << content_tag(:strong) do
- author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "hidden-xs")
+ author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "hidden-xs", tooltip: true)
author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "hidden-sm hidden-md hidden-lg")
end
end
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 72bd1fbbd81..2b0defd1dda 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -118,7 +118,7 @@ module IssuesHelper
end
def emoji_icon(name, unicode = nil, aliases = [], sprite: true)
- unicode ||= Emoji.emoji_filename(name) rescue ""
+ unicode ||= Gitlab::Emoji.emoji_filename(name) rescue ""
data = {
aliases: aliases.join(" "),
diff --git a/app/helpers/javascript_helper.rb b/app/helpers/javascript_helper.rb
index 91dd91718dc..0e456214d37 100644
--- a/app/helpers/javascript_helper.rb
+++ b/app/helpers/javascript_helper.rb
@@ -1,7 +1,5 @@
module JavascriptHelper
- def page_specific_javascripts(js = nil)
- @page_specific_javascripts = js unless js.nil?
-
- @page_specific_javascripts
+ def page_specific_javascript_tag(js)
+ 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..db6e731c744 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -27,7 +27,7 @@ module MergeRequestsHelper
end
def ci_build_details_path(merge_request)
- build_url = merge_request.source_project.ci_service.build_page(merge_request.last_commit.sha, merge_request.source_branch)
+ build_url = merge_request.source_project.ci_service.build_page(merge_request.diff_head_sha, merge_request.source_branch)
return nil unless build_url
parsed_url = URI.parse(build_url)
@@ -55,6 +55,10 @@ module MergeRequestsHelper
end.sort.to_sentence
end
+ def mr_closes_issues
+ @mr_closes_issues ||= @merge_request.closes_issues
+ end
+
def mr_change_branches_path(merge_request)
new_namespace_project_merge_request_path(
@project.namespace, @project,
@@ -92,4 +96,8 @@ module MergeRequestsHelper
["#{source_path}:#{source_branch}", "#{target_path}:#{target_branch}"]
end
end
+
+ def merge_request_button_visibility(merge_request, closed)
+ return 'hidden' if merge_request.closed? == closed || (merge_request.merged? == closed && !merge_request.closed?)
+ end
end
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index b401c8385be..98143dcee9b 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -24,28 +24,61 @@ module NotesHelper
}.to_json
end
- def link_to_new_diff_note(line_code, line_type = nil)
- discussion_id = LegacyDiffNote.build_discussion_id(
- @comments_target[:noteable_type],
- @comments_target[:noteable_id] || @comments_target[:commit_id],
- line_code
- )
+ def diff_view_data
+ return {} unless @comments_target
+
+ @comments_target.slice(:noteable_id, :noteable_type, :commit_id)
+ end
+
+ def diff_view_line_data(line_code, position, line_type)
+ return if @diff_notes_disabled
+
+ use_legacy_diff_note = @use_legacy_diff_notes
+ # If the controller doesn't force the use of legacy diff notes, we
+ # determine this on a line-by-line basis by seeing if there already exist
+ # active legacy diff notes at this line, in which case newly created notes
+ # will use the legacy technology as well.
+ # We do this because the discussion_id values of legacy and "new" diff
+ # notes, which are used to group notes on the merge request discussion tab,
+ # are incompatible.
+ # If we didn't, diff notes that would show for the same line on the changes
+ # tab, would show in different discussions on the discussion tab.
+ use_legacy_diff_note ||= begin
+ line_diff_notes = @grouped_diff_notes[line_code]
+ line_diff_notes && line_diff_notes.any?(&:legacy_diff_note?)
+ end
data = {
- noteable_type: @comments_target[:noteable_type],
- noteable_id: @comments_target[:noteable_id],
- commit_id: @comments_target[:commit_id],
- line_type: line_type,
- line_code: line_code,
- note_type: LegacyDiffNote.name,
- discussion_id: discussion_id
+ line_code: line_code,
+ line_type: line_type,
}
- button_tag(class: 'btn add-diff-note js-add-diff-note-button',
- data: data,
- title: 'Add a comment to this line') do
- icon('comment-o')
+ if use_legacy_diff_note
+ discussion_id = LegacyDiffNote.build_discussion_id(
+ @comments_target[:noteable_type],
+ @comments_target[:noteable_id] || @comments_target[:commit_id],
+ line_code
+ )
+
+ data.merge!(
+ note_type: LegacyDiffNote.name,
+ discussion_id: discussion_id
+ )
+ else
+ discussion_id = DiffNote.build_discussion_id(
+ @comments_target[:noteable_type],
+ @comments_target[:noteable_id] || @comments_target[:commit_id],
+ position
+ )
+
+ data.merge!(
+ position: position.to_json,
+ note_type: DiffNote.name,
+ discussion_id: discussion_id
+ )
end
+
+ data
end
def link_to_reply_discussion(note, line_type = nil)
@@ -60,13 +93,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
- button_tag 'Reply...', class: 'btn btn-text-field js-discussion-reply-button',
- data: data, title: 'Add a reply'
+ content_tag(:div, class: "discussion-reply-holder") do
+ button_tag 'Reply...', class: 'btn btn-text-field js-discussion-reply-button',
+ data: data, title: 'Add a reply'
+ end
+ end
+
+ def note_max_access_for_user(note)
+ @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..a733dff1579 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -15,11 +15,11 @@ 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)
- default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name" }
+ default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name", tooltip: false }
opts = default_opts.merge(opts)
return "(deleted)" unless author
@@ -27,13 +27,14 @@ 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]
author_html << content_tag(:span, sanitize("@#{author.username}"), class: opts[:author_class]) if opts[:name]
else
- author_html << content_tag(:span, sanitize(author.name), class: opts[:author_class]) if opts[:name]
+ tooltip_data = { placement: 'top' }
+ author_html << content_tag(:span, sanitize(author.name), class: [opts[:author_class], ('has-tooltip' if opts[:tooltip])], title: (author.to_reference if opts[:tooltip]), data: (tooltip_data if opts[:tooltip])) if opts[:name]
end
author_html << capture(&block) if block
@@ -60,7 +61,7 @@ module ProjectsHelper
project_link = link_to simple_sanitize(project.name), project_path(project), { class: "project-item-select-holder" }
if current_user
- project_link << icon("chevron-down", class: "dropdown-toggle-caret js-projects-dropdown-toggle", data: { target: ".js-dropdown-menu-projects", toggle: "dropdown" })
+ project_link << icon("chevron-down", class: "dropdown-toggle-caret js-projects-dropdown-toggle", aria: { label: "Toggle switch project dropdown" }, data: { target: ".js-dropdown-menu-projects", toggle: "dropdown" })
end
full_title = "#{namespace_link} / #{project_link}".html_safe
@@ -206,10 +207,14 @@ module ProjectsHelper
end
def default_clone_protocol
- if !current_user || current_user.require_ssh_key?
- gitlab_config.protocol
+ if allowed_protocols_present?
+ enabled_protocol
else
- "ssh"
+ if !current_user || current_user.require_ssh_key?
+ gitlab_config.protocol
+ else
+ 'ssh'
+ end
end
end
@@ -289,7 +294,11 @@ module ProjectsHelper
end
def last_push_event
- if current_user
+ return unless current_user
+
+ if fork = current_user.fork_of(@project)
+ current_user.recent_push(fork.id)
+ else
current_user.recent_push(@project.id)
end
end
@@ -327,9 +336,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..fcb2703e837 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
@@ -44,15 +43,15 @@ module SearchHelper
# Autocomplete results for internal help pages
def help_autocomplete
[
- { category: "Help", label: "API Help", url: help_page_path("api", "README") },
- { category: "Help", label: "Markdown Help", url: help_page_path("markdown", "markdown") },
- { category: "Help", label: "Permissions Help", url: help_page_path("permissions", "permissions") },
- { category: "Help", label: "Public Access Help", url: help_page_path("public_access", "public_access") },
- { category: "Help", label: "Rake Tasks Help", url: help_page_path("raketasks", "README") },
- { category: "Help", label: "SSH Keys Help", url: help_page_path("ssh", "README") },
- { category: "Help", label: "System Hooks Help", url: help_page_path("system_hooks", "system_hooks") },
- { category: "Help", label: "Webhooks Help", url: help_page_path("web_hooks", "web_hooks") },
- { category: "Help", label: "Workflow Help", url: help_page_path("workflow", "README") },
+ { category: "Help", label: "API Help", url: help_page_path("api/README") },
+ { category: "Help", label: "Markdown Help", url: help_page_path("markdown/markdown") },
+ { category: "Help", label: "Permissions Help", url: help_page_path("user/permissions") },
+ { category: "Help", label: "Public Access Help", url: help_page_path("public_access/public_access") },
+ { category: "Help", label: "Rake Tasks Help", url: help_page_path("raketasks/README") },
+ { category: "Help", label: "SSH Keys Help", url: help_page_path("ssh/README") },
+ { category: "Help", label: "System Hooks Help", url: help_page_path("system_hooks/system_hooks") },
+ { category: "Help", label: "Webhooks Help", url: help_page_path("web_hooks/web_hooks") },
+ { category: "Help", label: "Workflow Help", url: help_page_path("workflow/README") },
]
end
diff --git a/app/helpers/time_helper.rb b/app/helpers/time_helper.rb
index b04b0a5114c..1926f03af07 100644
--- a/app/helpers/time_helper.rb
+++ b/app/helpers/time_helper.rb
@@ -1,14 +1,4 @@
module TimeHelper
- def duration_in_words(finished_at, started_at)
- if finished_at && started_at
- interval_in_seconds = finished_at.to_i - started_at.to_i
- elsif started_at
- interval_in_seconds = Time.now.to_i - started_at.to_i
- end
-
- time_interval_in_words(interval_in_seconds)
- end
-
def time_interval_in_words(interval_in_seconds)
minutes = interval_in_seconds / 60
seconds = interval_in_seconds - minutes * 60
@@ -23,4 +13,21 @@ 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)
+ interval = interval_in_seconds(started_at, finished_at)
+ time_format = interval < 1.hour ? "%M:%S" : "%H:%M:%S"
+
+ Time.at(interval).utc.strftime(time_format)
+ end
+
+ private
+
+ def interval_in_seconds(started_at, finished_at = nil)
+ if started_at && finished_at
+ finished_at.to_i - started_at.to_i
+ elsif started_at
+ Time.now.to_i - started_at.to_i
+ end
+ end
end
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index a832a6c8df7..e3a208f826a 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -1,10 +1,10 @@
module TodosHelper
def todos_pending_count
- TodosFinder.new(current_user, state: :pending).execute.count
+ @todos_pending_count ||= TodosFinder.new(current_user, state: :pending).execute.count
end
def todos_done_count
- TodosFinder.new(current_user, state: :done).execute.count
+ @todos_done_count ||= TodosFinder.new(current_user, state: :done).execute.count
end
def todo_action_name(todo)
@@ -13,6 +13,7 @@ module TodosHelper
when Todo::MENTIONED then 'mentioned you on'
when Todo::BUILD_FAILED then 'The build failed for your'
when Todo::MARKED then 'added a todo for'
+ when Todo::APPROVAL_REQUIRED then 'set you as an approver for'
end
end
diff --git a/app/helpers/u2f_helper.rb b/app/helpers/u2f_helper.rb
new file mode 100644
index 00000000000..143b4ca6b51
--- /dev/null
+++ b/app/helpers/u2f_helper.rb
@@ -0,0 +1,5 @@
+module U2fHelper
+ def inject_u2f_api?
+ browser.chrome? && browser.version.to_i >= 41 && !browser.device.mobile?
+ end
+end
diff --git a/app/helpers/workhorse_helper.rb b/app/helpers/workhorse_helper.rb
index 2bd0dbfd095..d887cdadc34 100644
--- a/app/helpers/workhorse_helper.rb
+++ b/app/helpers/workhorse_helper.rb
@@ -1,4 +1,4 @@
-# Helpers to send Git blobs, diffs or archives through Workhorse.
+# Helpers to send Git blobs, diffs, patches or archives through Workhorse.
# Workhorse will also serve files when using `send_file`.
module WorkhorseHelper
# Send a Git blob through Workhorse
@@ -16,9 +16,22 @@ module WorkhorseHelper
head :ok
end
+ # Send a Git patch through Workhorse
+ def send_git_patch(repository, diff_refs)
+ headers.store(*Gitlab::Workhorse.send_git_patch(repository, diff_refs))
+ headers['Content-Disposition'] = 'inline'
+ head :ok
+ end
+
# Archive a Git repository and send it through Workhorse
def send_git_archive(repository, ref:, format:)
headers.store(*Gitlab::Workhorse.send_git_archive(repository, ref: ref, format: format))
head :ok
end
+
+ # Send an entry from artifacts through Workhorse
+ def send_artifacts_entry(build, entry)
+ headers.store(*Gitlab::Workhorse.send_artifacts_entry(build, entry))
+ head :ok
+ end
end
diff --git a/app/mailers/emails/builds.rb b/app/mailers/emails/builds.rb
index 2f86d1be576..3853af6201a 100644
--- a/app/mailers/emails/builds.rb
+++ b/app/mailers/emails/builds.rb
@@ -6,6 +6,7 @@ module Emails
add_project_headers
add_build_headers('failed')
+
mail(to: to, subject: subject("Build failed for #{@project.name}", @build.short_sha))
end
diff --git a/app/mailers/emails/members.rb b/app/mailers/emails/members.rb
index 6dde2e9847d..45311690293 100644
--- a/app/mailers/emails/members.rb
+++ b/app/mailers/emails/members.rb
@@ -12,6 +12,11 @@ module Emails
@member_id = member_id
admins = member_source.members.owners_and_masters.includes(:user).pluck(:notification_email)
+ # A project in a group can have no explicit owners/masters, in that case
+ # we fallbacks to the group's owners/masters.
+ if admins.empty? && member_source.respond_to?(:group) && member_source.group
+ admins = member_source.group.members.owners_and_masters.includes(:user).pluck(:notification_email)
+ end
mail(to: admins,
subject: subject("Request to join the #{member_source.human_name} #{member_source.model_name.singular}"))
diff --git a/app/models/ability.rb b/app/models/ability.rb
index 9c58b956007..6fd18f2ee24 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?
@@ -196,7 +203,9 @@ class Ability
@public_project_rules ||= project_guest_rules + [
:download_code,
:fork_project,
- :read_commit_status
+ :read_commit_status,
+ :read_pipeline,
+ :read_container_image
]
end
@@ -342,8 +351,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
@@ -351,7 +363,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,
@@ -360,6 +372,10 @@ class Ability
]
end
+ if group.public? || (group.internal? && !user.external?)
+ rules << :request_access unless group.users.include?(user)
+ end
+
rules.flatten
end
@@ -511,6 +527,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
@@ -549,5 +577,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 d823d67e3ba..cc4809b3abe 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -5,6 +5,7 @@ module Ci
belongs_to :erased_by, class_name: 'User'
serialize :options
+ serialize :yaml_variables
validates :coverage, numericality: true, allow_blank: true
validates_presence_of :ref
@@ -13,21 +14,20 @@ 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) }
+ scope :manual_actions, ->() { where(when: :manual) }
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
@@ -54,7 +54,10 @@ module Ci
new_build.stage = build.stage
new_build.stage_idx = build.stage_idx
new_build.trigger_request = build.trigger_request
+ new_build.yaml_variables = build.yaml_variables
+ new_build.when = build.when
new_build.user = user
+ new_build.environment = build.environment
new_build.save
MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build)
new_build
@@ -89,8 +92,31 @@ module Ci
end
end
+ def manual?
+ self.when == 'manual'
+ end
+
+ def other_actions
+ pipeline.manual_actions.where.not(id: self)
+ end
+
+ def playable?
+ project.builds_enabled? && commands.present? && manual?
+ end
+
+ def play(current_user = nil)
+ # Try to queue a current build
+ if self.queue
+ self.update(user: current_user)
+ self
+ else
+ # Otherwise we need to create a duplicate
+ Ci::Build.retry(self, current_user)
+ end
+ end
+
def retryable?
- project.builds_enabled? && commands.present?
+ project.builds_enabled? && commands.present? && complete?
end
def retried?
@@ -300,18 +326,12 @@ module Ci
project.valid_runners_token? token
end
- def can_be_served?(runner)
- return false unless has_tags? || runner.run_untagged?
-
- (tag_list - runner.tag_list).empty?
- end
-
def has_tags?
tag_list.any?
end
def any_runners_online?
- project.any_runners? { |runner| runner.active? && runner.online? && can_be_served?(runner) }
+ project.any_runners? { |runner| runner.active? && runner.online? && runner.can_pick?(self) }
end
def stuck?
@@ -335,7 +355,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!
@@ -379,38 +404,30 @@ module Ci
self.update(artifacts_expire_at: nil)
end
- private
-
- def erase_trace!
- self.trace = nil
+ def when
+ read_attribute(:when) || build_attributes_from_config[:when] || 'on_success'
end
- def update_erased!(user = nil)
- self.update(erased_by: user, erased_at: Time.now, artifacts_expire_at: nil)
+ def yaml_variables
+ read_attribute(:yaml_variables) || build_attributes_from_config[:yaml_variables] || []
end
- def yaml_variables
- global_yaml_variables + job_yaml_variables
+ private
+
+ def update_artifacts_size
+ self.artifacts_size = if artifacts_file.exists?
+ artifacts_file.size
+ else
+ nil
+ end
end
- def global_yaml_variables
- if pipeline.config_processor
- pipeline.config_processor.global_variables.map do |key, value|
- { key: key, value: value, public: true }
- end
- else
- []
- end
+ def erase_trace!
+ self.trace = nil
end
- def job_yaml_variables
- if pipeline.config_processor
- pipeline.config_processor.job_variables(name).map do |key, value|
- { key: key, value: value, public: true }
- end
- else
- []
- end
+ def update_erased!(user = nil)
+ self.update(erased_by: user, erased_at: Time.now, artifacts_expire_at: nil)
end
def project_variables
@@ -461,5 +478,11 @@ module Ci
variables << { key: 'CI_SERVER_URL', value: Gitlab::REVISION, public: true }
variables
end
+
+ def build_attributes_from_config
+ return {} unless pipeline.config_processor
+
+ pipeline.config_processor.build_attributes(name)
+ end
end
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 5b264ecffc5..aca8607f4e8 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -6,6 +6,8 @@ module Ci
self.table_name = 'ci_commits'
belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
+ belongs_to :user
+
has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id
has_many :builds, class_name: 'Ci::Build', foreign_key: :commit_id
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest', foreign_key: :commit_id
@@ -16,6 +18,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]
@@ -37,22 +40,26 @@ module Ci
end
def git_author_name
- commit_data.author_name if commit_data
+ commit.try(:author_name)
end
def git_author_email
- commit_data.author_email if commit_data
+ commit.try(:author_email)
end
def git_commit_message
- commit_data.message if commit_data
+ commit.try(:message)
+ end
+
+ def git_commit_title
+ commit.try(:title)
end
def short_sha
Ci::Pipeline.truncate_sha(sha)
end
- def commit_data
+ def commit
@commit ||= project.commit(sha)
rescue
nil
@@ -62,6 +69,10 @@ module Ci
!tag?
end
+ def manual_actions
+ builds.latest.manual_actions
+ end
+
def retryable?
builds.latest.any? do |build|
build.failed? && build.retryable?
@@ -163,13 +174,26 @@ 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
builds.where.not(environment: nil).success.pluck(:environment).uniq
end
+ # Manually set the notes for a Ci::Pipeline
+ # There is no ActiveRecord relation between Ci::Pipeline and notes
+ # as they are related to a commit sha. This method helps importing
+ # them using the +Gitlab::ImportExport::RelationFactory+ class.
+ def notes=(notes)
+ notes.each do |note|
+ note[:id] = nil
+ note[:commit_id] = sha
+ note[:noteable_id] = self['id']
+ note.save!
+ end
+ end
+
def notes
Note.for_commit_id(sha)
end
@@ -199,5 +223,12 @@ module Ci
self.duration = statuses.latest.duration
save
end
+
+ def keep_around_commits
+ return unless project
+
+ project.repository.keep_around(self.sha)
+ project.repository.keep_around(self.before_sha)
+ end
end
end
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index adb65292208..b64ec79ec2b 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -4,7 +4,7 @@ module Ci
LAST_CONTACT_TIME = 5.minutes.ago
AVAILABLE_SCOPES = %w[specific shared active paused online]
- FORM_EDITABLE = %i[description tag_list active run_untagged]
+ FORM_EDITABLE = %i[description tag_list active run_untagged locked]
has_many :builds, class_name: 'Ci::Build'
has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
@@ -26,6 +26,13 @@ module Ci
.where("ci_runner_projects.gl_project_id = :project_id OR ci_runners.is_shared = true", project_id: project_id)
end
+ scope :assignable_for, ->(project) do
+ # FIXME: That `to_sql` is needed to workaround a weird Rails bug.
+ # Without that, placeholders would miss one and couldn't match.
+ where(locked: false).
+ where.not("id IN (#{project.runners.select(:id).to_sql})").specific
+ end
+
validate :tag_constraints
acts_as_taggable
@@ -56,7 +63,7 @@ module Ci
def assign_to(project, current_user = nil)
self.is_shared = false if shared?
self.save
- project.runner_projects.create!(runner_id: self.id)
+ project.runner_projects.create(runner_id: self.id)
end
def display_name
@@ -91,6 +98,10 @@ module Ci
!shared?
end
+ def can_pick?(build)
+ assignable_for?(build.project) && accepting_tags?(build)
+ end
+
def only_for?(project)
projects == [project]
end
@@ -111,5 +122,13 @@ module Ci
'can not be empty when runner is not allowed to pick untagged jobs')
end
end
+
+ def assignable_for?(project)
+ !locked? || projects.exists?(id: project.id)
+ end
+
+ def accepting_tags?(build)
+ (run_untagged? || build.has_tags?) && (build.tag_list - tag_list).empty?
+ 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 d69d518fadd..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
@@ -271,6 +278,32 @@ class Commit
merged_merge_request ? 'merge request' : 'commit'
end
+ # Get the URI type of the given path
+ #
+ # Used to build URLs to files in the repository in GFM.
+ #
+ # path - String path to check
+ #
+ # Examples:
+ #
+ # uri_type('doc/README.md') # => :blob
+ # uri_type('doc/logo.png') # => :raw
+ # uri_type('doc/api') # => :tree
+ # uri_type('not/found') # => :nil
+ #
+ # Returns a symbol
+ def uri_type(path)
+ entry = @raw.tree.path(path)
+ if entry[:type] == :blob
+ blob = Gitlab::Git::Blob.new(name: entry[:name])
+ blob.image? ? :raw : :blob
+ else
+ entry[:type]
+ end
+ rescue Rugged::TreeError
+ nil
+ end
+
private
def repo_changes
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/commit_status.rb b/app/models/commit_status.rb
index ab13db4b297..535db26240a 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -8,6 +8,8 @@ class CommitStatus < ActiveRecord::Base
belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :commit_id, touch: true
belongs_to :user
+ delegate :commit, to: :pipeline
+
validates :pipeline, presence: true, unless: :importing?
validates_presence_of :name
@@ -20,6 +22,10 @@ class CommitStatus < ActiveRecord::Base
scope :ignored, -> { where(allow_failure: true, status: [:failed, :canceled]) }
state_machine :status, initial: :pending do
+ event :queue do
+ transition skipped: :pending
+ end
+
event :run do
transition pending: :running
end
diff --git a/app/models/concerns/awardable.rb b/app/models/concerns/awardable.rb
index 539c7c31e30..800a16ab246 100644
--- a/app/models/concerns/awardable.rb
+++ b/app/models/concerns/awardable.rb
@@ -2,10 +2,11 @@ module Awardable
extend ActiveSupport::Concern
included do
- has_many :award_emoji, as: :awardable, dependent: :destroy
+ has_many :award_emoji, -> { includes(:user) }, as: :awardable, dependent: :destroy
if self < Participable
- participant :award_emoji_with_associations
+ # By default we always load award_emoji user association
+ participant :award_emoji
end
end
@@ -34,12 +35,9 @@ module Awardable
end
end
- def award_emoji_with_associations
- award_emoji.includes(:user)
- end
-
def grouped_awards(with_thumbs: true)
- awards = award_emoji_with_associations.group_by(&:name)
+ # By default we always load award_emoji user association
+ awards = award_emoji.group_by(&:name)
if with_thumbs
awards[AwardEmoji::UPVOTE_NAME] ||= []
@@ -67,8 +65,7 @@ module Awardable
def create_award_emoji(name, current_user)
return unless emoji_awardable?
-
- award_emoji.create(name: name, user: current_user)
+ award_emoji.create(name: normalize_name(name), user: current_user)
end
def remove_award_emoji(name, current_user)
@@ -82,4 +79,10 @@ module Awardable
create_award_emoji(emoji_name, current_user)
end
end
+
+ private
+
+ def normalize_name(name)
+ Gitlab::AwardEmoji.normalize_emoji_name(name)
+ end
end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 0ccd3474b81..acb6f5a2998 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -19,9 +19,14 @@ module Issuable
belongs_to :milestone
has_many :notes, as: :noteable, dependent: :destroy do
def authors_loaded?
- # We check first if we're loaded to not load unnecesarily.
+ # We check first if we're loaded to not load unnecessarily.
loaded? && to_a.all? { |note| note.association(:author).loaded? }
end
+
+ def award_emojis_loaded?
+ # We check first if we're loaded to not load unnecessarily.
+ loaded? && to_a.all? { |note| note.association(:award_emoji).loaded? }
+ end
end
has_many :label_links, as: :target, dependent: :destroy
has_many :labels, through: :label_links
@@ -49,11 +54,10 @@ module Issuable
scope :without_label, -> { joins("LEFT OUTER JOIN label_links ON label_links.target_type = '#{name}' AND label_links.target_id = #{table_name}.id").where(label_links: { id: nil }) }
scope :join_project, -> { joins(:project) }
- scope :inc_notes_with_associations, -> { includes(notes: :author) }
+ scope :inc_notes_with_associations, -> { includes(notes: [ :project, :author, :award_emoji ]) }
scope :references_project, -> { references(:project) }
scope :non_archived, -> { join_project.where(projects: { archived: false }) }
-
delegate :name,
:email,
to: :author,
@@ -112,15 +116,18 @@ module Issuable
end
def sort(method, excluded_labels: [])
- case method.to_s
- when 'milestone_due_asc' then order_milestone_due_asc
- when 'milestone_due_desc' then order_milestone_due_desc
- when 'downvotes_desc' then order_downvotes_desc
- when 'upvotes_desc' then order_upvotes_desc
- when 'priority' then order_labels_priority(excluded_labels: excluded_labels)
- else
- order_by(method)
- end
+ sorted = case method.to_s
+ when 'milestone_due_asc' then order_milestone_due_asc
+ when 'milestone_due_desc' then order_milestone_due_desc
+ when 'downvotes_desc' then order_downvotes_desc
+ when 'upvotes_desc' then order_upvotes_desc
+ when 'priority' then order_labels_priority(excluded_labels: excluded_labels)
+ else
+ order_by(method)
+ end
+
+ # Break ties with the ID column for pagination
+ sorted.order(id: :desc)
end
def order_labels_priority(excluded_labels: [])
@@ -257,7 +264,14 @@ module Issuable
# already have their authors loaded (possibly because the scope
# `inc_notes_with_associations` was used) and skip the inclusion if that's
# the case.
- notes.authors_loaded? ? notes : notes.includes(:author)
+ includes = []
+ includes << :author unless notes.authors_loaded?
+ includes << :award_emoji unless notes.award_emojis_loaded?
+ if includes.any?
+ notes.includes(includes)
+ else
+ notes
+ end
end
def updated_tasks
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index f00b5b8497c..ec9e0f1b1d0 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -14,14 +14,14 @@ module Mentionable
attr = attr.to_s
mentionable_attrs << [attr, options]
end
+ end
+ included do
# Accessor for attributes marked mentionable.
- def mentionable_attrs
- @mentionable_attrs ||= []
+ cattr_accessor :mentionable_attrs, instance_accessor: false do
+ []
end
- end
- included do
if self < Participable
participant -> (user, ext) { all_references(user, extractor: ext) }
end
@@ -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/concerns/participable.rb b/app/models/concerns/participable.rb
index 9056722f45e..70740c76e43 100644
--- a/app/models/concerns/participable.rb
+++ b/app/models/concerns/participable.rb
@@ -41,9 +41,12 @@ module Participable
def participant(attr)
participant_attrs << attr
end
+ end
- def participant_attrs
- @participant_attrs ||= []
+ included do
+ # Accessor for participant attributes.
+ cattr_accessor :participant_attrs, instance_accessor: false do
+ []
end
end
@@ -53,6 +56,16 @@ module Participable
#
# Returns an Array of User instances.
def participants(current_user = nil)
+ @participants ||= Hash.new do |hash, user|
+ hash[user] = raw_participants(user)
+ end
+
+ @participants[current_user]
+ end
+
+ private
+
+ def raw_participants(current_user = nil)
current_user ||= author
ext = Gitlab::ReferenceExtractor.new(project, current_user)
participants = Set.new
diff --git a/app/models/concerns/statuseable.rb b/app/models/concerns/statuseable.rb
index 3ef91caad47..44c6b30f278 100644
--- a/app/models/concerns/statuseable.rb
+++ b/app/models/concerns/statuseable.rb
@@ -16,10 +16,10 @@ module Statuseable
deduce_status = "(CASE
WHEN (#{builds})=0 THEN NULL
- WHEN (#{builds})=(#{success})+(#{ignored}) THEN 'success'
- WHEN (#{builds})=(#{pending}) THEN 'pending'
- WHEN (#{builds})=(#{canceled})+(#{success})+(#{ignored}) THEN 'canceled'
WHEN (#{builds})=(#{skipped}) THEN 'skipped'
+ WHEN (#{builds})=(#{success})+(#{ignored})+(#{skipped}) THEN 'success'
+ WHEN (#{builds})=(#{pending})+(#{skipped}) THEN 'pending'
+ WHEN (#{builds})=(#{canceled})+(#{success})+(#{ignored})+(#{skipped}) THEN 'canceled'
WHEN (#{running})+(#{pending})>0 THEN 'running'
ELSE 'failed'
END)"
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index e498ca96e3c..1a7cd60817e 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,12 @@ class Deployment < ActiveRecord::Base
def last?
self == environment.last_deployment
end
+
+ def keep_around_commit
+ project.repository.keep_around(self.sha)
+ end
+
+ def manual_actions
+ deployable.try(:other_actions)
+ 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/key.rb b/app/models/key.rb
index 0532e84f47d..b9bc38a0436 100644
--- a/app/models/key.rb
+++ b/app/models/key.rb
@@ -9,7 +9,7 @@ class Key < ActiveRecord::Base
before_validation :strip_white_space, :generate_fingerprint
validates :title, presence: true, length: { within: 0..255 }
- validates :key, presence: true, length: { within: 0..5000 }, format: { with: /\A(ssh|ecdsa)-.*\Z/ }, uniqueness: true
+ validates :key, presence: true, length: { within: 0..5000 }, format: { with: /\A(ssh|ecdsa)-.*\Z/ }
validates :key, format: { without: /\n|\r/, message: 'should be a single line' }
validates :fingerprint, uniqueness: true, presence: { message: 'cannot be generated' }
diff --git a/app/models/label.rb b/app/models/label.rb
index 49c352cc239..35e678001dc 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -20,10 +20,10 @@ class Label < ActiveRecord::Base
validates :color, color: true, allow_blank: false
validates :project, presence: true, unless: Proc.new { |service| service.template? }
- # Don't allow '?', '&', and ',' for label titles
+ # Don't allow ',' for label titles
validates :title,
presence: true,
- format: { with: /\A[^&\?,]+\z/ },
+ format: { with: /\A[^,]+\z/ },
uniqueness: { scope: :project_id }
before_save :nullify_priority
@@ -52,14 +52,17 @@ class Label < ActiveRecord::Base
# This pattern supports cross-project references.
#
def self.reference_pattern
+ # NOTE: The id pattern only matches when all characters on the expression
+ # are digits, so it will match ~2 but not ~2fa because that's probably a
+ # label name and we want it to be matched as such.
@reference_pattern ||= %r{
(#{Project.reference_pattern})?
#{Regexp.escape(reference_prefix)}
(?:
- (?<label_id>\d+) | # Integer-based label ID, or
+ (?<label_id>\d+(?!\S\w)\b) | # Integer-based label ID, or
(?<label_name>
- [A-Za-z0-9_-]+ | # String-based single-word label title, or
- "[^&\?,]+" # String-based multi-word label surrounded in quotes
+ [A-Za-z0-9_\-\?\.&]+ | # String-based single-word label title, or
+ ".+?" # String-based multi-word label surrounded in quotes
)
)
}x
@@ -114,7 +117,7 @@ class Label < ActiveRecord::Base
end
def title=(value)
- write_attribute(:title, Sanitize.clean(value.to_s)) if value.present?
+ write_attribute(:title, sanitize_title(value)) if value.present?
end
private
@@ -132,4 +135,8 @@ class Label < ActiveRecord::Base
def nullify_priority
self.priority = nil if priority.blank?
end
+
+ def sanitize_title(value)
+ CGI.unescapeHTML(Sanitize.clean(value.to_s))
+ end
end
diff --git a/app/models/legacy_diff_note.rb b/app/models/legacy_diff_note.rb
index 95fd510eb3a..04a651d50ab 100644
--- a/app/models/legacy_diff_note.rb
+++ b/app/models/legacy_diff_note.rb
@@ -1,4 +1,6 @@
class LegacyDiffNote < Note
+ include NoteOnDiff
+
serialize :st_diff
validates :line_code, presence: true, line_code: true
@@ -11,77 +13,36 @@ 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
- @discussion_id ||= self.class.build_discussion_id(noteable_type, noteable_id || commit_id, line_code, active?)
+ @discussion_id ||= self.class.build_discussion_id(noteable_type, noteable_id || commit_id, line_code)
end
def diff_file_hash
line_code.split('_')[0] if line_code
end
- def diff_old_line
- line_code.split('_')[1].to_i if line_code
- end
-
- def diff_new_line
- line_code.split('_')[2].to_i if line_code
- end
-
def diff
@diff ||= Gitlab::Git::Diff.new(st_diff) if st_diff.respond_to?(:map)
end
- def diff_file_path
- diff.new_path.presence || diff.old_path
- end
-
- def diff_lines
- @diff_lines ||= Gitlab::Diff::Parser.new.parse(diff.diff.each_line)
+ def diff_file
+ @diff_file ||= Gitlab::Diff::File.new(diff, repository: self.project.repository) if diff
end
def diff_line
- @diff_line ||= diff_lines.find { |line| generate_line_code(line) == self.line_code }
+ @diff_line ||= diff_file.line_for_line_code(self.line_code) if diff_file
end
- def diff_line_text
- diff_line.try(:text)
- end
-
- def diff_line_type
- diff_line.try(:type)
- end
-
- def highlighted_diff_lines
- Gitlab::Diff::Highlight.new(diff_lines).highlight
- end
-
- def truncated_diff_lines
- max_number_of_lines = 16
- prev_match_line = nil
- prev_lines = []
-
- highlighted_diff_lines.each do |line|
- if line.type == "match"
- prev_lines.clear
- prev_match_line = line
- else
- prev_lines << line
-
- break if generate_line_code(line) == self.line_code
-
- prev_lines.shift if prev_lines.length >= max_number_of_lines
- end
- end
-
- prev_lines
+ def for_line?(line)
+ !line.meta? && diff_file.line_code(line) == self.line_code
end
# Check if this note is part of an "active" discussion
@@ -94,7 +55,7 @@ class LegacyDiffNote < Note
def active?
return @active if defined?(@active)
return true if for_commit?
- return true unless self.diff
+ return true unless diff_line
return false unless noteable
noteable_diff = find_noteable_diff
@@ -102,7 +63,7 @@ class LegacyDiffNote < Note
if noteable_diff
parsed_lines = Gitlab::Diff::Parser.new.parse(noteable_diff.diff.each_line)
- @active = parsed_lines.any? { |line_obj| line_obj.text == diff_line_text }
+ @active = parsed_lines.any? { |line_obj| line_obj.text == diff_line.text }
else
@active = false
end
@@ -110,10 +71,6 @@ class LegacyDiffNote < Note
@active
end
- def award_emoji_supported?
- false
- end
-
private
def find_diff
@@ -149,10 +106,6 @@ class LegacyDiffNote < Note
self.class.where(attributes).last.try(:diff)
end
- def generate_line_code(line)
- Gitlab::Diff::LineCode.generate(diff_file_path, line.new_pos, line.old_pos)
- end
-
# Find the diff on noteable that matches our own
def find_noteable_diff
diffs = noteable.diffs(Commit.max_diff_options)
diff --git a/app/models/member.rb b/app/models/member.rb
index 4ee3f1bb5c2..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) }
@@ -48,7 +47,6 @@ class Member < ActiveRecord::Base
after_create :post_create_hook, unless: [:pending?, :importing?]
after_update :post_update_hook, unless: [:pending?, :importing?]
after_destroy :post_destroy_hook, unless: :pending?
- after_destroy :post_decline_request, if: :request?
delegate :name, :username, :email, to: :user, prefix: true
@@ -188,7 +186,7 @@ class Member < ActiveRecord::Base
end
def send_request
- # override in subclass
+ notification_service.new_access_request(self)
end
def post_create_hook
@@ -215,10 +213,6 @@ class Member < ActiveRecord::Base
post_create_hook
end
- def post_decline_request
- # override in subclass
- end
-
def system_hook_service
SystemHooksService.new
end
diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb
index 363db877968..2f13d339c89 100644
--- a/app/models/members/group_member.rb
+++ b/app/models/members/group_member.rb
@@ -33,12 +33,6 @@ class GroupMember < Member
super
end
- def send_request
- notification_service.new_group_access_request(self)
-
- super
- end
-
def post_create_hook
notification_service.new_group_member(self)
@@ -64,10 +58,4 @@ class GroupMember < Member
super
end
-
- def post_decline_request
- notification_service.decline_group_access_request(self)
-
- super
- end
end
diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb
index 250ee04fd1d..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
@@ -111,12 +110,6 @@ class ProjectMember < Member
super
end
- def send_request
- notification_service.new_project_access_request(self)
-
- super
- end
-
def post_create_hook
unless owner?
event_service.join_project(self.project, self.user)
@@ -152,12 +145,6 @@ class ProjectMember < Member
super
end
- def post_decline_request
- notification_service.decline_project_access_request(self)
-
- super
- end
-
def event_service
EventCreateService.new
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 36bc98bdb1e..471e32f3b60 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -12,12 +12,14 @@ 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
+ delegate :commits, :real_size, to: :merge_request_diff, prefix: nil
# When this attribute is true some MR validation is ignored
# It allows us to close or modify broken merge requests
@@ -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,28 +160,99 @@ class MergeRequest < ActiveRecord::Base
reference
end
- def last_commit
- merge_request_diff ? merge_request_diff.last_commit : compare_commits.last
- end
-
def first_commit
merge_request_diff ? merge_request_diff.first_commit : compare_commits.first
end
+ def diffs(*args)
+ merge_request_diff ? merge_request_diff.diffs(*args) : compare.diffs(*args)
+ end
+
def diff_size
merge_request_diff.size
end
def diff_base_commit
- if merge_request_diff
+ if persisted?
merge_request_diff.base_commit
- elsif source_sha
- self.target_project.merge_base_commit(self.source_sha, self.target_branch)
+ elsif diff_start_commit && diff_head_commit
+ self.target_project.merge_base_commit(diff_start_sha, diff_head_sha)
end
end
- def last_commit_short_sha
- last_commit.short_id
+ # MRs created before 8.4 don't store a MergeRequestDiff#base_commit_sha,
+ # but we need to get a commit for the "View file @ ..." link by deleted files,
+ # so we find the likely one if we can't get the actual one.
+ # This will not be the actual base commit if the target branch was merged into
+ # the source branch after the merge request was created, but it is good enough
+ # for the specific purpose of linking to a commit.
+ # It is not good enough for use in `Gitlab::Git::DiffRefs`, which needs the
+ # true base commit, so we can't simply have `#diff_base_commit` fall back on
+ # this method.
+ def likely_diff_base_commit
+ first_commit.parent || first_commit
+ end
+
+ def diff_start_commit
+ if persisted?
+ merge_request_diff.start_commit
+ else
+ target_branch_head
+ end
+ end
+
+ def diff_head_commit
+ if persisted?
+ merge_request_diff.head_commit
+ else
+ source_branch_head
+ end
+ end
+
+ def 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 +289,30 @@ class MergeRequest < ActiveRecord::Base
def update_merge_request_diff
if source_branch_changed? || target_branch_changed?
- reload_code
+ reload_diff
end
end
- def reload_code
- if merge_request_diff && open?
- merge_request_diff.reload_content
- end
+ def reload_diff
+ return unless merge_request_diff && open?
+
+ old_diff_refs = self.diff_refs
+
+ merge_request_diff.reload_content
+
+ new_diff_refs = self.diff_refs
+
+ update_diff_notes_positions(
+ old_diff_refs: old_diff_refs,
+ new_diff_refs: new_diff_refs
+ )
end
def check_if_can_be_merged
return unless unchecked?
can_be_merged =
- !broken? && project.repository.can_be_merged?(source_sha, target_branch)
+ !broken? && project.repository.can_be_merged?(diff_head_sha, target_branch)
if can_be_merged
mark_as_mergeable
@@ -247,11 +322,11 @@ class MergeRequest < ActiveRecord::Base
end
def merge_event
- self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::MERGED).last
+ @merge_event ||= target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::MERGED).last
end
def closed_event
- self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last
+ @closed_event ||= target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last
end
WIP_REGEX = /\A\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i.freeze
@@ -264,19 +339,19 @@ class MergeRequest < ActiveRecord::Base
self.title.sub(WIP_REGEX, "")
end
- def mergeable?
- return false unless mergeable_state?
+ def mergeable?(skip_ci_check: false)
+ return false unless mergeable_state?(skip_ci_check: skip_ci_check)
check_if_can_be_merged
can_be_merged?
end
- def mergeable_state?
+ def mergeable_state?(skip_ci_check: false)
return false unless open?
return false if work_in_progress?
return false if broken?
- return false unless mergeable_ci_state?
+ return false unless skip_ci_check || mergeable_ci_state?
true
end
@@ -289,7 +364,7 @@ class MergeRequest < ActiveRecord::Base
!source_project.protected_branch?(source_branch) &&
!source_project.root_ref?(source_branch) &&
Ability.abilities.allowed?(current_user, :push_code, source_project) &&
- last_commit == source_project.commit(source_branch)
+ diff_head_commit == source_branch_head
end
def should_remove_source_branch?
@@ -319,13 +394,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 +402,8 @@ class MergeRequest < ActiveRecord::Base
work_in_progress: work_in_progress?
}
- if last_commit
- attrs.merge!(last_commit: last_commit.hook_attrs)
+ if diff_head_commit
+ attrs.merge!(last_commit: diff_head_commit.hook_attrs)
end
attributes.merge!(attrs)
@@ -484,7 +552,13 @@ class MergeRequest < ActiveRecord::Base
end
def can_be_merged_by?(user)
- ::Gitlab::GitAccess.new(user, project).can_push_to_branch?(target_branch)
+ access = ::Gitlab::UserAccess.new(user, project: project)
+ access.can_push_to_branch?(target_branch) || access.can_merge_to_branch?(target_branch)
+ end
+
+ def can_be_merged_via_command_line_by?(user)
+ access = ::Gitlab::UserAccess.new(user, project: project)
+ access.can_push_to_branch?(target_branch)
end
def mergeable_ci_state?
@@ -513,22 +587,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 +599,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 +619,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 +632,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 +643,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 +657,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..3f520c8f3ff 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -1,13 +1,14 @@
class MergeRequestDiff < ActiveRecord::Base
include Sortable
include Importable
+ include EncodingHelper
# Prevent store of diff if commits amount more then 500
COMMITS_SAFE_SIZE = 100
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 +25,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,14 +40,15 @@ class MergeRequestDiff < ActiveRecord::Base
if options[:ignore_whitespace_change]
@diffs_no_whitespace ||= begin
compare = Gitlab::Git::Compare.new(
- self.repository.raw_repository,
- self.base,
- self.head,
+ repository.raw_repository,
+ self.start_commit_sha || self.target_branch_sha,
+ self.head_commit_sha || self.source_branch_sha,
)
compare.diffs(options)
end
else
- @diffs ||= load_diffs(st_diffs, options)
+ @diffs ||= {}
+ @diffs[options] ||= load_diffs(st_diffs, options)
end
end
@@ -62,37 +65,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 +110,142 @@ 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)
+ if paths = options[:paths]
+ raw = raw.select do |diff|
+ paths.include?(diff[:old_path]) || paths.include?(diff[:new_path])
+ end
+ end
+
+ Gitlab::Git::DiffCollection.new(raw, options)
+ else
+ Gitlab::Git::DiffCollection.new([])
+ end
end
# Reload diffs between branches related to current merge request from repo
# and save it as array of hashes in st_diffs db field
def reload_diffs
+ 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
- self.base_commit_sha = self.repository.merge_base(self.head, self.base)
+ 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.save
+ update_columns_serialized(new_attributes)
+
+ 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
-
- def head
- self.source_sha
+ def utf8_st_diffs
+ st_diffs.map do |diff|
+ diff.each do |k, v|
+ diff[k] = encode_utf8(v) if v.respond_to?(:encoding)
+ end
+ end
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
+ #
+ # #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?
+
+ update_columns(new_attributes.merge(updated_at: current_time_from_proper_timezone))
+ reload
+ 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 8d164647550..0ce10c77de9 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -6,6 +6,14 @@ class Note < ActiveRecord::Base
include Awardable
include Importable
+ # Attribute containing rendered and redacted Markdown as generated by
+ # Banzai::ObjectRenderer.
+ attr_accessor :note_html
+
+ # An Array containing the number of visible references as generated by
+ # Banzai::ObjectRenderer
+ attr_accessor :user_visible_reference_count
+
default_value_for :system, false
attr_mentionable :note, pipeline: :note
@@ -17,6 +25,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
@@ -49,16 +58,19 @@ class Note < ActiveRecord::Base
scope :fresh, ->{ order(created_at: :asc, id: :asc) }
scope :inc_author_project, ->{ includes(:project, :author) }
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
+ # FYI noteable cannot be loaded for LegacyDiffNote for commits
includes(:author, :noteable, :updated_by,
project: [:project_members, { group: [:group_members] }])
end
before_validation :clear_blank_line_code!
+ after_save :keep_around_commit
class << self
def model_name
@@ -74,7 +86,7 @@ class Note < ActiveRecord::Base
end
def grouped_diff_notes
- legacy_diff_notes.select(&:active?).sort_by(&:created_at).group_by(&:line_code)
+ diff_notes.select(&:active?).sort_by(&:created_at).group_by(&:line_code)
end
# Searches for notes matching the given query.
@@ -107,6 +119,10 @@ class Note < ActiveRecord::Base
false
end
+ def new_diff_note?
+ false
+ end
+
def active?
true
end
@@ -181,11 +197,19 @@ class Note < ActiveRecord::Base
end
def cross_reference_not_visible_for?(user)
- cross_reference? && referenced_mentionables(user).empty?
+ cross_reference? && !has_referenced_mentionables?(user)
+ end
+
+ def has_referenced_mentionables?(user)
+ if user_visible_reference_count.present?
+ user_visible_reference_count > 0
+ else
+ referenced_mentionables(user).any?
+ end
end
def award_emoji?
- award_emoji_supported? && contains_emoji_only?
+ can_be_award_emoji? && contains_emoji_only?
end
def emoji_awardable?
@@ -196,7 +220,7 @@ class Note < ActiveRecord::Base
self.line_code = nil if self.line_code.blank?
end
- def award_emoji_supported?
+ def can_be_award_emoji?
noteable.is_a?(Awardable)
end
@@ -205,7 +229,12 @@ class Note < ActiveRecord::Base
end
def award_emoji_name
- original_name = note.match(Banzai::Filter::EmojiFilter.emoji_pattern)[1]
- Gitlab::AwardEmoji.normalize_emoji_name(original_name)
+ note.match(Banzai::Filter::EmojiFilter.emoji_pattern)[1]
+ end
+
+ private
+
+ 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..a805f5d97bc 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,9 +461,11 @@ class Project < ActiveRecord::Base
end
def import_url=(value)
+ return super(value) unless Gitlab::UrlSanitizer.valid?(value)
+
import_url = Gitlab::UrlSanitizer.new(value)
- create_or_update_import_data(credentials: import_url.credentials)
super(import_url.sanitized_url)
+ create_or_update_import_data(credentials: import_url.credentials)
end
def import_url
@@ -460,7 +477,13 @@ class Project < ActiveRecord::Base
end
end
+ def valid_import_url?
+ valid? || errors.messages[:import_url].nil?
+ end
+
def create_or_update_import_data(data: nil, credentials: nil)
+ return unless import_url.present? && valid_import_url?
+
project_import_data = import_data || build_import_data
if data
project_import_data.data ||= {}
@@ -539,6 +562,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 +707,7 @@ class Project < ActiveRecord::Base
end
def avatar_url
- if avatar.present?
+ if self[:avatar].present?
[gitlab_config.url, avatar.url].join
elsif avatar_in_git
Gitlab::Routing.url_helpers.namespace_project_avatar_url(namespace, self)
@@ -775,18 +808,12 @@ class Project < ActiveRecord::Base
@repo_exists = false
end
+ # Branches that are not _exactly_ matched by a protected branch.
def open_branches
- # We're using a Set here as checking values in a large Set is faster than
- # checking values in a large Array.
- protected_set = Set.new(protected_branch_names)
-
- repository.branches.reject do |branch|
- protected_set.include?(branch.name)
- end
- end
-
- def protected_branch_names
- @protected_branch_names ||= protected_branches.pluck(:name)
+ exact_protected_branch_names = protected_branches.reject(&:wildcard?).map(&:name)
+ branch_names = repository.branches.map(&:name)
+ non_open_branch_names = Set.new(exact_protected_branch_names).intersection(Set.new(branch_names))
+ repository.branches.reject { |branch| non_open_branch_names.include? branch.name }
end
def root_ref?(branch)
@@ -803,11 +830,16 @@ 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 developers_can_merge_to_protected_branch?(branch_name)
+ protected_branches.matching(branch_name).any?(&:developers_can_merge)
end
def forked?
@@ -830,12 +862,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 +1008,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
@@ -1010,8 +1042,8 @@ class Project < ActiveRecord::Base
pipelines.order(id: :desc).find_by(sha: sha, ref: ref)
end
- def ensure_pipeline(sha, ref)
- pipeline(sha, ref) || pipelines.create(sha: sha, ref: ref)
+ def ensure_pipeline(sha, ref, current_user = nil)
+ pipeline(sha, ref) || pipelines.create(sha: sha, ref: ref, user: current_user)
end
def enable_ci
@@ -1128,4 +1160,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/builds_email_service.rb b/app/models/project_services/builds_email_service.rb
index 54da4d74fc5..5e166471077 100644
--- a/app/models/project_services/builds_email_service.rb
+++ b/app/models/project_services/builds_email_service.rb
@@ -42,6 +42,19 @@ class BuildsEmailService < Service
end
end
+ def can_test?
+ project.builds.count > 0
+ end
+
+ def disabled_title
+ "Please setup a build on your repository."
+ end
+
+ def test_data(project = nil, user = nil)
+ build = project.builds.last
+ Gitlab::BuildDataBuilder.build(build)
+ end
+
def fields
[
{ type: 'textarea', name: 'recipients', placeholder: 'Emails separated by comma' },
@@ -50,6 +63,20 @@ class BuildsEmailService < Service
]
end
+ def test(data)
+ begin
+ # bypass build status verification when testing
+ data[:build_status] = "failed"
+ data[:build_allow_failure] = false
+
+ result = execute(data)
+ rescue StandardError => error
+ return { success: false, result: error }
+ end
+
+ { success: true, result: result }
+ end
+
def should_build_be_notified?(data)
case data[:build_status]
when 'success'
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 bbd7682d8e7..1a2ac90da51 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
@@ -130,7 +130,7 @@ class Repository
end
def find_tag(name)
- raw_repository.tags.find { |tag| tag.name == name }
+ tags.find { |tag| tag.name == name }
end
def add_branch(user, branch_name, target)
@@ -191,14 +191,38 @@ class Repository
end
end
+ def ref_names
+ branch_names + tag_names
+ end
+
def branch_names
- cache.fetch(:branch_names) { branches.map(&:name) }
+ @branch_names ||= cache.fetch(:branch_names) { branches.map(&:name) }
end
def branch_exists?(branch_name)
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
@@ -242,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
@@ -267,6 +293,7 @@ class Repository
def expire_branches_cache
cache.expire(:branch_names)
+ @branch_names = nil
@local_branches = nil
end
@@ -281,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)
@@ -332,10 +357,6 @@ class Repository
@lookup_cache ||= {}
end
- def expire_branch_names
- cache.expire(:branch_names)
- end
-
def expire_avatar_cache(branch_name = nil, revision = nil)
# Avatars are pulled from the default branch, thus if somebody pushes to a
# different branch there's no need to expire anything.
@@ -632,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
@@ -693,6 +704,7 @@ class Repository
options[:commit] = {
message: message,
branch: ref,
+ update_ref: false,
}
raw_repository.mkdir(path, options)
@@ -708,6 +720,7 @@ class Repository
options[:commit] = {
message: message,
branch: ref,
+ update_ref: false,
}
options[:file] = {
@@ -720,6 +733,33 @@ class Repository
end
end
+ def update_file(user, path, content, branch:, previous_path:, message:)
+ commit_with_hooks(user, branch) do |ref|
+ committer = user_to_committer(user)
+ options = {}
+ options[:committer] = committer
+ options[:author] = committer
+ options[:commit] = {
+ message: message,
+ branch: ref,
+ update_ref: false
+ }
+
+ options[:file] = {
+ content: content,
+ path: path,
+ update: true
+ }
+
+ if previous_path
+ options[:file][:previous_path] = previous_path
+ Gitlab::Git::Blob.rename(raw_repository, options)
+ else
+ Gitlab::Git::Blob.commit(raw_repository, options)
+ end
+ end
+ end
+
def remove_file(user, path, message, branch)
commit_with_hooks(user, branch) do |ref|
committer = user_to_committer(user)
@@ -728,7 +768,8 @@ class Repository
options[:author] = committer
options[:commit] = {
message: message,
- branch: ref
+ branch: ref,
+ update_ref: false,
}
options[:file] = {
@@ -758,9 +799,9 @@ class Repository
end
end
- def merge(user, source_sha, target_branch, options = {})
- our_commit = rugged.branches[target_branch].target
- their_commit = rugged.lookup(source_sha)
+ def merge(user, merge_request, options = {})
+ our_commit = rugged.branches[merge_request.target_branch].target
+ their_commit = rugged.lookup(merge_request.diff_head_sha)
raise "Invalid merge target" if our_commit.nil?
raise "Invalid merge source" if their_commit.nil?
@@ -768,14 +809,15 @@ class Repository
merge_index = rugged.merge_commits(our_commit, their_commit)
return false if merge_index.conflicts?
- commit_with_hooks(user, target_branch) do |ref|
+ commit_with_hooks(user, merge_request.target_branch) do
actual_options = options.merge(
parents: [our_commit, their_commit],
tree: merge_index.write_tree(rugged),
- update_ref: ref
)
- Rugged::Commit.create(rugged, actual_options)
+ commit_id = Rugged::Commit.create(rugged, actual_options)
+ merge_request.update(in_progress_merge_commit_sha: commit_id)
+ commit_id
end
end
@@ -785,15 +827,14 @@ class Repository
return false unless revert_tree_id
- commit_with_hooks(user, base_branch) do |ref|
+ commit_with_hooks(user, base_branch) do
committer = user_to_committer(user)
source_sha = Rugged::Commit.create(rugged,
message: commit.revert_message,
author: committer,
committer: committer,
tree: revert_tree_id,
- parents: [rugged.lookup(source_sha)],
- update_ref: ref)
+ parents: [rugged.lookup(source_sha)])
end
end
@@ -803,7 +844,7 @@ class Repository
return false unless cherry_pick_tree_id
- commit_with_hooks(user, base_branch) do |ref|
+ commit_with_hooks(user, base_branch) do
committer = user_to_committer(user)
source_sha = Rugged::Commit.create(rugged,
message: commit.message,
@@ -814,8 +855,7 @@ class Repository
},
committer: committer,
tree: cherry_pick_tree_id,
- parents: [rugged.lookup(source_sha)],
- update_ref: ref)
+ parents: [rugged.lookup(source_sha)])
end
end
@@ -874,7 +914,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})
@@ -891,7 +930,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
@@ -917,20 +956,6 @@ class Repository
Gitlab::Popen.popen(args, path_to_repo)
end
- def with_tmp_ref(oldrev = nil)
- random_string = SecureRandom.hex
- tmp_ref = "refs/tmp/#{random_string}/head"
-
- if oldrev && !Gitlab::Git.blank_ref?(oldrev)
- rugged.references.create(tmp_ref, oldrev)
- end
-
- # Make commit in tmp ref
- yield(tmp_ref)
- ensure
- rugged.references.delete(tmp_ref) rescue nil
- end
-
def commit_with_hooks(current_user, branch)
update_autocrlf_option
@@ -943,33 +968,31 @@ class Repository
oldrev = target_branch.target
end
- with_tmp_ref(oldrev) do |tmp_ref|
- # Make commit in tmp ref
- newrev = yield(tmp_ref)
+ # Make commit
+ newrev = yield(ref)
- unless newrev
- raise CommitError.new('Failed to create commit')
- end
+ unless newrev
+ raise CommitError.new('Failed to create commit')
+ end
- GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
- if was_empty || !target_branch
- # Create branch
- rugged.references.create(ref, newrev)
+ GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
+ if was_empty || !target_branch
+ # Create branch
+ rugged.references.create(ref, newrev)
+ else
+ # Update head
+ current_head = find_branch(branch).target
+
+ # Make sure target branch was not changed during pre-receive hook
+ if current_head == oldrev
+ rugged.references.update(ref, newrev)
else
- # Update head
- current_head = find_branch(branch).target
-
- # Make sure target branch was not changed during pre-receive hook
- if current_head == oldrev
- rugged.references.update(ref, newrev)
- else
- raise CommitError.new('Commit was rejected because branch received new push')
- end
+ raise CommitError.new('Commit was rejected because branch received new push')
end
end
-
- newrev
end
+
+ newrev
end
def ls_files(ref)
@@ -977,6 +1000,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
@@ -1014,4 +1041,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..f4bcb49b34d 100644
--- a/app/models/sent_notification.rb
+++ b/app/models/sent_notification.rb
@@ -1,4 +1,6 @@
class SentNotification < ActiveRecord::Base
+ serialize :position, Gitlab::Diff::Position
+
belongs_to :project
belongs_to :noteable, polymorphic: true
belongs_to :recipient, class_name: "User"
@@ -7,7 +9,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
- record(note.noteable, recipient_id, reply_key, params)
+ attrs.merge!(note.diff_attributes)
+ end
+
+ record(note.noteable, recipient_id, reply_key, attrs)
end
end
@@ -64,7 +72,51 @@ class SentNotification < ActiveRecord::Base
end
end
+ def position=(new_position)
+ if new_position.is_a?(String)
+ new_position = JSON.parse(new_position) rescue nil
+ end
+
+ if new_position.is_a?(Hash)
+ new_position = new_position.with_indifferent_access
+ new_position = Gitlab::Diff::Position.new(new_position)
+ end
+
+ super(new_position)
+ end
+
def to_param
self.reply_key
end
+
+ def note_attributes
+ {
+ project: self.project,
+ author: self.recipient,
+ type: self.note_type,
+ noteable_type: self.noteable_type,
+ noteable_id: self.noteable_id,
+ commit_id: self.commit_id,
+ line_code: self.line_code,
+ position: self.position.to_json
+ }
+ end
+
+ def create_note(note)
+ Notes::CreateService.new(
+ self.project,
+ self.recipient,
+ self.note_attributes.merge(note: note)
+ ).execute
+ end
+
+ private
+
+ def note_valid
+ Note.new(note_attributes.merge(note: "Test")).valid?
+ end
+
+ def keep_around_commit
+ project.repository.keep_around(self.commit_id)
+ end
end
diff --git a/app/models/service.rb b/app/models/service.rb
index 40d39933ad8..5432f8c7ab4 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -76,6 +76,10 @@ class Service < ActiveRecord::Base
[]
end
+ def test_data(project, user)
+ Gitlab::PushDataBuilder.build_sample(project, user)
+ end
+
def supported_events
%w(push tag_push issue merge_request wiki_page)
end
@@ -94,6 +98,11 @@ class Service < ActiveRecord::Base
!project.empty_repo?
end
+ # reason why service cannot be tested
+ def disabled_title
+ "Please setup a project repository."
+ end
+
# Provide convenient accessor methods
# for each serialized property.
# Also keep track of updated properties in a similar way as ActiveModel::Dirty
@@ -170,6 +179,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..8d7a5965aa1 100644
--- a/app/models/todo.rb
+++ b/app/models/todo.rb
@@ -1,8 +1,17 @@
class Todo < ActiveRecord::Base
- ASSIGNED = 1
- MENTIONED = 2
- BUILD_FAILED = 3
- MARKED = 4
+ ASSIGNED = 1
+ MENTIONED = 2
+ BUILD_FAILED = 3
+ MARKED = 4
+ APPROVAL_REQUIRED = 5 # This is an EE-only feature
+
+ ACTION_NAMES = {
+ ASSIGNED => :assigned,
+ MENTIONED => :mentioned,
+ BUILD_FAILED => :build_failed,
+ MARKED => :marked,
+ APPROVAL_REQUIRED => :approval_required
+ }
belongs_to :author, class_name: "User"
belongs_to :note
@@ -30,10 +39,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 +77,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 2e458329cb9..975e935fa20 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
@@ -84,9 +85,10 @@ class User < ActiveRecord::Base
has_one :abuse_report, dependent: :destroy
has_many :spam_logs, dependent: :destroy
has_many :builds, dependent: :nullify, class_name: 'Ci::Build'
+ has_many :pipelines, dependent: :nullify, class_name: 'Ci::Pipeline'
has_many :todos, dependent: :destroy
has_many :notification_settings, dependent: :destroy
- has_many :award_emoji, as: :awardable, dependent: :destroy
+ has_many :award_emoji, dependent: :destroy
#
# Validations
@@ -308,7 +310,7 @@ class User < ActiveRecord::Base
def generate_password
if self.force_random_password
- self.password = self.password_confirmation = Devise.friendly_token.first(8)
+ self.password = self.password_confirmation = Devise.friendly_token.first(Devise.password_length.min)
end
end
@@ -487,9 +489,8 @@ class User < ActiveRecord::Base
events.recent.find do |event|
project = Project.find_by_id(event.project_id)
next unless project
- repo = project.repository
- if repo.branch_names.include?(event.branch_name)
+ if project.repository.branch_exists?(event.branch_name)
merge_requests = MergeRequest.where("created_at >= ?", event.created_at).
where(source_project_id: project.id,
source_branch: event.branch_name)
@@ -653,7 +654,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)
@@ -764,7 +765,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
@@ -852,9 +853,8 @@ 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 } }
+ scope = { access_level: Gitlab::Access.all_values.select { |access| access >= min_access_level } }
relations = [relations.shift] + relations.map { |relation| relation.where(members: scope) }
end
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/ci/create_builds_service.rb b/app/services/ci/create_builds_service.rb
index 2dcb052d274..4946f7076fd 100644
--- a/app/services/ci/create_builds_service.rb
+++ b/app/services/ci/create_builds_service.rb
@@ -15,7 +15,7 @@ module Ci
status == 'success'
when 'on_failure'
status == 'failed'
- when 'always'
+ when 'always', 'manual'
%w(success failed).include?(status)
end
end
@@ -36,7 +36,9 @@ module Ci
:allow_failure,
:stage,
:stage_idx,
- :environment)
+ :environment,
+ :when,
+ :yaml_variables)
build_attrs.merge!(pipeline: @pipeline,
ref: @pipeline.ref,
@@ -45,6 +47,10 @@ module Ci
user: user,
project: @pipeline.project)
+ # TODO: The proper implementation for this is in
+ # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5295
+ build_attrs[:status] = 'skipped' if build_attrs[:when] == 'manual'
+
##
# We do not persist new builds here.
# Those will be persisted when @pipeline is saved.
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index b1ee6874190..be91bf0db85 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -2,6 +2,7 @@ module Ci
class CreatePipelineService < BaseService
def execute
pipeline = project.pipelines.new(params)
+ pipeline.user = current_user
unless ref_names.include?(params[:ref])
pipeline.errors.add(:base, 'Reference not found')
diff --git a/app/services/ci/register_build_service.rb b/app/services/ci/register_build_service.rb
index f0ed09a629a..9a187f5d694 100644
--- a/app/services/ci/register_build_service.rb
+++ b/app/services/ci/register_build_service.rb
@@ -21,7 +21,7 @@ module Ci
end
build = builds.find do |build|
- build.can_be_served?(current_runner)
+ current_runner.can_pick?(build)
end
if build
diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb
index 6b69cb53b2c..ed73d8cb8c2 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::UserAccess.new(current_user, project: project).can_push_to_branch?(@target_branch)
unless allowed
raise ValidationError.new('You are not allowed to push into this branch')
@@ -31,7 +31,7 @@ module Commits
true
end
-
+
def create_target_branch(new_branch)
# Temporary branch exists and contains the change commit
return success if repository.find_branch(new_branch)
diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb
index e2bccbdbcc3..149822aa647 100644
--- a/app/services/compare_service.rb
+++ b/app/services/compare_service.rb
@@ -3,7 +3,7 @@ require 'securerandom'
# Compare 2 branches for one repo or between repositories
# and return Gitlab::Git::Compare object that responds to commits and diffs
class CompareService
- def execute(source_project, source_branch, target_project, target_branch, diff_options = {})
+ def execute(source_project, source_branch, target_project, target_branch)
source_commit = source_project.commit(source_branch)
return unless source_commit
diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb
index 9f4481a8153..757fc35a78f 100644
--- a/app/services/create_branch_service.rb
+++ b/app/services/create_branch_service.rb
@@ -3,44 +3,42 @@ 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(
- source_project.repository.path_to_repo,
- "refs/heads/#{ref}",
- tmp_ref
- )
-
- new_branch = repository.add_branch(current_user, branch_name, tmp_ref)
- end
- else
- new_branch = repository.add_branch(current_user, branch_name, ref)
- end
+ new_branch = if source_project != @project
+ repository.fetch_ref(
+ source_project.repository.path_to_repo,
+ "refs/heads/#{ref}",
+ "refs/heads/#{branch_name}"
+ )
+
+ repository.after_create_branch
+
+ repository.find_branch(branch_name)
+ else
+ repository.add_branch(current_user, branch_name, ref)
+ 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_commit_builds_service.rb b/app/services/create_commit_builds_service.rb
index f947e8f452e..0b66b854dea 100644
--- a/app/services/create_commit_builds_service.rb
+++ b/app/services/create_commit_builds_service.rb
@@ -14,7 +14,13 @@ class CreateCommitBuildsService
return false
end
- @pipeline = Ci::Pipeline.new(project: project, sha: sha, ref: ref, before_sha: before_sha, tag: tag)
+ @pipeline = Ci::Pipeline.new(
+ project: project,
+ sha: sha,
+ ref: ref,
+ before_sha: before_sha,
+ tag: tag,
+ user: user)
##
# Skip creating pipeline if no gitlab-ci.yml is found
diff --git a/app/services/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..c4a206f785e 100644
--- a/app/services/files/base_service.rb
+++ b/app/services/files/base_service.rb
@@ -9,6 +9,7 @@ module Files
@commit_message = params[:commit_message]
@file_path = params[:file_path]
+ @previous_path = params[:previous_path]
@file_content = if params[:file_content_encoding] == 'base64'
Base64.decode64(params[:file_content])
else
@@ -26,7 +27,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 +44,7 @@ module Files
end
def validate
- allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(@target_branch)
+ allowed = ::Gitlab::UserAccess.new(current_user, project: project).can_push_to_branch?(@target_branch)
unless allowed
raise_error("You are not allowed to push into this branch")
@@ -51,12 +52,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/files/update_service.rb b/app/services/files/update_service.rb
index 1960dc7d949..8d2b5083179 100644
--- a/app/services/files/update_service.rb
+++ b/app/services/files/update_service.rb
@@ -3,7 +3,10 @@ require_relative "base_service"
module Files
class UpdateService < Files::BaseService
def commit
- repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch, true)
+ repository.update_file(current_user, @file_path, @file_content,
+ branch: @target_branch,
+ previous_path: @previous_path,
+ message: @commit_message)
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_push_service.rb b/app/services/git_push_service.rb
index a886f35981f..e02b50ff9a2 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -89,7 +89,8 @@ class GitPushService < BaseService
# Set protection on the default branch if configured
if current_application_settings.default_branch_protection != PROTECTION_NONE
developers_can_push = current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_PUSH ? true : false
- @project.protected_branches.create({ name: @project.default_branch, developers_can_push: developers_can_push })
+ developers_can_merge = current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_MERGE ? true : false
+ @project.protected_branches.create({ name: @project.default_branch, developers_can_push: developers_can_push, developers_can_merge: developers_can_merge })
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/issuable_base_service.rb b/app/services/issuable_base_service.rb
index e3dc569152c..2d96efe1042 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -101,6 +101,7 @@ class IssuableBaseService < BaseService
def update(issuable)
change_state(issuable)
+ change_subscription(issuable)
filter_params
old_labels = issuable.labels.to_a
@@ -124,6 +125,15 @@ class IssuableBaseService < BaseService
end
end
+ def change_subscription(issuable)
+ case params.delete(:subscription_event)
+ when 'subscribe'
+ issuable.subscribe(current_user)
+ when 'unsubscribe'
+ issuable.unsubscribe(current_user)
+ end
+ end
+
def has_changes?(issuable, old_labels: [])
valid_attrs = [:title, :description, :assignee_id, :milestone_id, :target_branch]
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..7e19a73f71a 100644
--- a/app/services/issues/bulk_update_service.rb
+++ b/app/services/issues/bulk_update_service.rb
@@ -4,11 +4,12 @@ module Issues
issues_ids = params.delete(:issues_ids).split(",")
issue_params = params
- %i(state_event milestone_id assignee_id add_label_ids remove_label_ids).each do |key|
+ %i(state_event milestone_id assignee_id add_label_ids remove_label_ids subscription_event).each do |key|
issue_params.delete(key) unless issue_params[key].present?
end
issues = Issue.where(id: issues_ids)
+
issues.each do |issue|
next unless can?(current_user, :update_issue, issue)
diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb
new file mode 100644
index 00000000000..15358f80208
--- /dev/null
+++ b/app/services/members/destroy_service.rb
@@ -0,0 +1,21 @@
+module Members
+ class DestroyService < BaseService
+ attr_accessor :member, :current_user
+
+ def initialize(member, user)
+ @member, @current_user = member, user
+ end
+
+ def execute
+ unless member && can?(current_user, "destroy_#{member.type.underscore}".to_sym, member)
+ raise Gitlab::Access::AccessDeniedError
+ end
+
+ member.destroy
+
+ if member.request? && member.user != current_user
+ notification_service.decline_access_request(member)
+ end
+ end
+ end
+end
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/build_service.rb b/app/services/merge_requests/build_service.rb
index 1b48899bb0a..7fe57747265 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -83,7 +83,7 @@ module MergeRequests
closes_issue = "Closes ##{iid}"
if merge_request.description.present?
- merge_request.description << closes_issue.prepend("\n")
+ merge_request.description += closes_issue.prepend("\n")
else
merge_request.description = closes_issue
end
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index 9aaf5a5e561..0dac0614141 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -34,12 +34,17 @@ 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, 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
+ ensure
+ merge_request.update(in_progress_merge_commit_sha: nil)
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..1daf6bbf553 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|
@@ -48,7 +48,7 @@ module MergeRequests
end
def force_push?
- Gitlab::ForcePushCheck.force_push?(@project, @oldrev, @newrev)
+ Gitlab::Checks::ForcePush.force_push?(@project, @oldrev, @newrev)
end
# Refresh merge request diff if we push to source or target branch of 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/create_service.rb b/app/services/notes/create_service.rb
index 02fca5c0ea3..18971bd0be3 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -8,7 +8,6 @@ module Notes
if note.award_emoji?
noteable = note.noteable
todo_service.new_award_emoji(noteable, current_user)
-
return noteable.create_award_emoji(note.award_emoji_name, current_user)
end
diff --git a/app/services/notes/diff_position_update_service.rb b/app/services/notes/diff_position_update_service.rb
new file mode 100644
index 00000000000..0cb731f5bc3
--- /dev/null
+++ b/app/services/notes/diff_position_update_service.rb
@@ -0,0 +1,30 @@
+module Notes
+ class DiffPositionUpdateService < BaseService
+ def execute(note)
+ new_position = tracer.trace(note.position)
+
+ # Don't update the position if the type doesn't match, since that means
+ # the diff line commented on was changed, and the comment is now outdated
+ old_position = note.position
+ if new_position &&
+ new_position != old_position &&
+ new_position.type == old_position.type
+
+ note.position = new_position
+ end
+
+ note
+ end
+
+ private
+
+ def tracer
+ @tracer ||= Gitlab::Diff::PositionTracer.new(
+ repository: project.repository,
+ old_diff_refs: params[:old_diff_refs],
+ new_diff_refs: params[:new_diff_refs],
+ paths: params[:paths]
+ )
+ end
+ end
+end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index 19832a19b2b..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,20 +177,22 @@ 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
end
- # Project access request
- def new_project_access_request(project_member)
- mailer.member_access_requested_email(project_member.real_source_type, project_member.id).deliver_later
+ # Members
+ def new_access_request(member)
+ mailer.member_access_requested_email(member.real_source_type, member.id).deliver_later
end
- def decline_project_access_request(project_member)
- mailer.member_access_denied_email(project_member.real_source_type, project_member.project.id, project_member.user.id).deliver_later
+ def decline_access_request(member)
+ mailer.member_access_denied_email(member.real_source_type, member.source_id, member.user_id).deliver_later
end
+ # Project invite
def invite_project_member(project_member, token)
mailer.member_invited_email(project_member.real_source_type, project_member.id, token).deliver_later
end
@@ -216,15 +219,7 @@ class NotificationService
mailer.member_access_granted_email(project_member.real_source_type, project_member.id).deliver_later
end
- # Group access request
- def new_group_access_request(group_member)
- mailer.member_access_requested_email(group_member.real_source_type, group_member.id).deliver_later
- end
-
- def decline_group_access_request(group_member)
- mailer.member_access_denied_email(group_member.real_source_type, group_member.group.id, group_member.user.id).deliver_later
- end
-
+ # Group invite
def invite_group_member(group_member, token)
mailer.member_invited_email(group_member.real_source_type, group_member.id, token).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..29b3981f49f 100644
--- a/app/services/projects/housekeeping_service.rb
+++ b/app/services/projects/housekeeping_service.rb
@@ -7,8 +7,6 @@
#
module Projects
class HousekeepingService < BaseService
- include Gitlab::ShellAdapter
-
LEASE_TIMEOUT = 3600
class LeaseTaken < StandardError
@@ -24,11 +22,7 @@ module Projects
def execute
raise LeaseTaken unless try_obtain_lease
- GitlabShellOneShotWorker.perform_async(:gc, @project.path_with_namespace)
- ensure
- Gitlab::Metrics.measure(:reset_pushes_since_gc) do
- @project.update_column(:pushes_since_gc, 0)
- end
+ execute_gitlab_shell_gc
end
def needed?
@@ -36,13 +30,27 @@ module Projects
end
def increment!
- Gitlab::Metrics.measure(:increment_pushes_since_gc) do
- @project.increment!(:pushes_since_gc)
+ if Gitlab::ExclusiveLease.new("project_housekeeping:increment!:#{@project.id}", timeout: 60).try_obtain
+ Gitlab::Metrics.measure(:increment_pushes_since_gc) do
+ update_pushes_since_gc(@project.pushes_since_gc + 1)
+ end
end
end
private
+ def execute_gitlab_shell_gc
+ GitGarbageCollectWorker.perform_async(@project.id)
+ ensure
+ Gitlab::Metrics.measure(:reset_pushes_since_gc) do
+ update_pushes_since_gc(0)
+ end
+ end
+
+ def update_pushes_since_gc(new_value)
+ @project.update_column(:pushes_since_gc, new_value)
+ end
+
def try_obtain_lease
Gitlab::Metrics.measure(:obtain_housekeeping_lease) do
lease = ::Gitlab::ExclusiveLease.new("project_housekeeping:#{@project.id}", timeout: LEASE_TIMEOUT)
diff --git a/app/services/projects/import_export/export_service.rb b/app/services/projects/import_export/export_service.rb
index 80c7193efcb..06252c7b625 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
@@ -10,8 +9,8 @@ module Projects
private
def save_all
- if [version_saver, project_tree_saver, uploads_saver, repo_saver, wiki_repo_saver].all?(&:save)
- Gitlab::ImportExport::Saver.save(shared: @shared)
+ if [version_saver, avatar_saver, project_tree_saver, uploads_saver, repo_saver, wiki_repo_saver].all?(&:save)
+ Gitlab::ImportExport::Saver.save(project: project, shared: @shared)
notify_success
else
cleanup_and_notify
@@ -22,6 +21,10 @@ module Projects
Gitlab::ImportExport::VersionSaver.new(shared: @shared)
end
+ def avatar_saver
+ Gitlab::ImportExport::AvatarSaver.new(project: project, shared: @shared)
+ end
+
def project_tree_saver
Gitlab::ImportExport::ProjectTreeSaver.new(project: project, shared: @shared)
end
@@ -39,6 +42,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 +51,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..cdad0426b02 100644
--- a/app/services/projects/import_service.rb
+++ b/app/services/projects/import_service.rb
@@ -42,8 +42,8 @@ module Projects
def import_repository
begin
- gitlab_shell.import_repository(project.path_with_namespace, project.import_url)
- rescue Gitlab::Shell::Error => e
+ gitlab_shell.import_repository(project.repository_storage_path, project.path_with_namespace, project.import_url)
+ rescue => e
raise Error, "Error importing repository #{project.import_url} into #{project.path_with_namespace} - #{e.message}"
end
end
diff --git a/app/services/projects/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 941df08995c..f06311511cc 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -3,10 +3,11 @@ module Projects
def execute
# check that user is allowed to set specified visibility_level
new_visibility = params[:visibility_level]
+
if new_visibility && new_visibility.to_i != project.visibility_level
unless can?(current_user, :change_visibility_level, project) &&
Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
-
+
deny_visibility_level(project, new_visibility)
return project
end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 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..6b48d68cccb 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
@@ -193,7 +194,7 @@ class TodoService
end
def create_assignment_todo(issuable, author)
- if issuable.assignee && issuable.assignee != author
+ if issuable.assignee
attributes = attributes_for_todo(issuable.project, issuable, author, Todo::ASSIGNED)
create_todos(issuable.assignee, attributes)
end
@@ -236,9 +237,8 @@ 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
end
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/avatar_uploader.rb b/app/uploaders/avatar_uploader.rb
index 6135c3ad96f..03d9329a14e 100644
--- a/app/uploaders/avatar_uploader.rb
+++ b/app/uploaders/avatar_uploader.rb
@@ -14,4 +14,8 @@ class AvatarUploader < CarrierWave::Uploader::Base
def reset_events_cache(file)
model.reset_events_cache if model.is_a?(User)
end
+
+ def exists?
+ model.avatar.file && model.avatar.file.exists?
+ end
end
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/appearances/_form.html.haml b/app/views/admin/appearances/_form.html.haml
index d88f3ad314d..92e2dae4842 100644
--- a/app/views/admin/appearances/_form.html.haml
+++ b/app/views/admin/appearances/_form.html.haml
@@ -13,7 +13,7 @@
.col-sm-10
= f.text_area :description, class: "form-control", rows: 10
.hint
- Description parsed with #{link_to "GitLab Flavored Markdown", help_page_path('markdown', 'markdown'), target: '_blank'}.
+ Description parsed with #{link_to "GitLab Flavored Markdown", help_page_path('markdown/markdown'), target: '_blank'}.
.form-group
= f.label :logo, class: 'control-label'
.col-sm-10
@@ -46,7 +46,7 @@
Maximum file size is 1MB. Pages are optimized for a 72x72 px header logo
.form-actions
- = f.submit 'Save', class: 'btn btn-save'
+ = f.submit 'Save', class: 'btn btn-save append-right-10'
- if @appearance.persisted?
= link_to 'Preview last save', preview_admin_appearances_path, class: 'btn', target: '_blank'
diff --git a/app/views/admin/appearances/preview.html.haml b/app/views/admin/appearances/preview.html.haml
index dd4a64e80bc..6c51639b840 100644
--- a/app/views/admin/appearances/preview.html.haml
+++ b/app/views/admin/appearances/preview.html.haml
@@ -1,29 +1,9 @@
- page_title "Preview | Appearance"
-%h3.page-title
- Appearance settings - Preview
-%hr
+.login-box
+ .login-heading
+ %h3 Existing user? Sign in
+ %form
+ = text_field_tag :login, nil, class: "form-control top", placeholder: "Username or Email"
+ = password_field_tag :password, nil, class: "form-control bottom", placeholder: "Password"
+ = button_tag "Sign in", class: "btn-create btn"
-.ui-box
- .title
- Sign-in page
- %div
- .login-page
- .container
- .content
- .login-title
- %h1= brand_title
- %hr
- .container
- .content
- .row
- .col-sm-7
- .brand-image
- = brand_image
- .brand_text
- = brand_text
- .col-sm-4
- .login-box
- %h3.page-title Sign in
- = text_field_tag :login, nil, class: "form-control top", placeholder: "Username or Email"
- = password_field_tag :password, nil, class: "form-control bottom", placeholder: "Password"
- = button_tag "Sign in", class: "btn-create btn"
diff --git a/app/views/admin/appearances/show.html.haml b/app/views/admin/appearances/show.html.haml
index 089e8e4cb7a..454b779842c 100644
--- a/app/views/admin/appearances/show.html.haml
+++ b/app/views/admin/appearances/show.html.haml
@@ -1,7 +1,9 @@
- page_title "Appearance"
+
%h3.page-title
Appearance settings
%p.light
You can modify the look and feel of GitLab here
+%hr
= render 'form'
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index c883e8f97da..538d8176ce7 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)
@@ -38,11 +38,17 @@
= source
%span.help-block#import-sources-help
Enabled sources for code import during project creation. OmniAuth must be configured for GitHub
- = link_to "(?)", help_page_path("integration", "github")
+ = link_to "(?)", help_page_path("integration/github")
, Bitbucket
- = link_to "(?)", help_page_path("integration", "bitbucket")
+ = link_to "(?)", help_page_path("integration/bitbucket")
and GitLab.com
- = link_to "(?)", help_page_path("integration", "gitlab")
+ = link_to "(?)", help_page_path("integration/gitlab")
+ .form-group
+ %label.control-label.col-sm-2 Enabled Git access protocols
+ .col-sm-10
+ = select(:application_setting, :enabled_git_access_protocol, [['Both SSH and HTTP(S)', nil], ['Only SSH', 'ssh'], ['Only HTTP(S)', 'http']], {}, class: 'form-control')
+ %span.help-block#clone-protocol-help
+ Allow only the selected protocols to be used for Git access.
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
@@ -94,6 +100,13 @@
= f.label :user_oauth_applications do
= f.check_box :user_oauth_applications
Allow users to register any application to use GitLab as an OAuth provider
+ .form-group
+ = f.label :user_default_external, 'New users set to external', class: 'control-label col-sm-2'
+ .col-sm-10
+ .checkbox
+ = f.label :user_default_external do
+ = f.check_box :user_default_external
+ Newly registered users will by default be external
%fieldset
%legend Sign-in Restrictions
@@ -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/application_settings/show.html.haml b/app/views/admin/application_settings/show.html.haml
index e9c7ca9d5aa..ecc46d86afe 100644
--- a/app/views/admin/application_settings/show.html.haml
+++ b/app/views/admin/application_settings/show.html.haml
@@ -1,4 +1,5 @@
- page_title "Settings"
+
%h3.page-title Settings
%hr
= render 'form'
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/_build.html.haml b/app/views/admin/builds/_build.html.haml
index 967151bc33b..ce818c30c30 100644
--- a/app/views/admin/builds/_build.html.haml
+++ b/app/views/admin/builds/_build.html.haml
@@ -1,30 +1,40 @@
- project = build.project
-%tr.build
+%tr.build.commit
%td.status
= ci_status_with_icon(build.status)
- %td.build-link
- - if can?(current_user, :read_build, build.project)
- = link_to namespace_project_build_url(build.project.namespace, build.project, build) do
- %strong Build ##{build.id}
- - else
- %strong Build ##{build.id}
+ %td
+ .branch-commit
+ - if can?(current_user, :read_build, build.project)
+ = link_to namespace_project_build_url(build.project.namespace, build.project, build) do
+ %span.build-link ##{build.id}
+ - else
+ %span.build-link ##{build.id}
- - if build.stuck?
- %i.fa.fa-warning.text-warning
+ - if build.stuck?
+ %i.fa.fa-warning.text-warning
- %td
- - if project
- = link_to project.name_with_namespace, admin_namespace_project_path(project.namespace, project)
+ - if build.ref
+ = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name"
+ - else
+ .light none
+ = custom_icon("icon_commit")
- %td
- = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace"
+ = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace commit-id"
+
+ .label-container
+ - if build.tags.any?
+ - build.tags.each do |tag|
+ %span.label.label-primary
+ = tag
+ - if build.try(:trigger_request)
+ %span.label.label-info triggered
+ - if build.try(:allow_failure)
+ %span.label.label-danger allowed to fail
%td
- - if build.ref
- = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref)
- - else
- .light none
+ - if project
+ = link_to project.name_with_namespace, admin_namespace_project_path(project.namespace, project)
%td
- if build.try(:runner)
@@ -36,22 +46,15 @@
#{build.stage} / #{build.name}
%td
- - if build.tags.any?
- - build.tags.each do |tag|
- %span.label.label-primary
- = tag
- - if build.try(:trigger_request)
- %span.label.label-info triggered
- - if build.try(:allow_failure)
- %span.label.label-danger allowed to fail
-
- %td.duration
- if build.duration
- #{duration_in_words(build.finished_at, build.started_at)}
+ %p.duration
+ = custom_icon("icon_timer")
+ = duration_in_numbers(build.finished_at, build.started_at)
- %td.timestamp
- if build.finished_at
- %span #{time_ago_with_tooltip(build.finished_at)}
+ %p.finished-at
+ = icon("calendar")
+ %span #{time_ago_with_tooltip(build.finished_at)}
- if defined?(coverage) && coverage
%td.coverage
diff --git a/app/views/admin/builds/index.html.haml b/app/views/admin/builds/index.html.haml
index efd5b12cfeb..3d77634d8fa 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
@@ -10,15 +10,20 @@
All
%span.badge.js-totalbuilds-count= @all_builds.count(:id)
+ %li{class: ('active' if @scope == 'pending')}
+ = link_to admin_builds_path(scope: :pending) do
+ Pending
+ %span.badge= number_with_delimiter(@all_builds.pending.count(:id))
+
%li{class: ('active' if @scope == 'running')}
= link_to admin_builds_path(scope: :running) do
Running
- %span.badge.js-running-count= number_with_delimiter(@all_builds.running_or_pending.count(:id))
+ %span.badge= number_with_delimiter(@all_builds.running.count(:id))
%li{class: ('active' if @scope == 'finished')}
= link_to admin_builds_path(scope: :finished) do
Finished
- %span.badge.js-running-count= number_with_delimiter(@all_builds.finished.count(:id))
+ %span.badge= number_with_delimiter(@all_builds.finished.count(:id))
.nav-controls
- if @all_builds.running_or_pending.any?
@@ -27,7 +32,7 @@
.row-content-block.second-block
#{(@scope || 'all').capitalize} builds
- %ul.content-list
+ %ul.content-list.builds-content-list
- if @builds.blank?
%li
.nothing-here-block No builds to show
@@ -37,15 +42,11 @@
%thead
%tr
%th Status
- %th Build ID
- %th Project
%th Commit
- %th Ref
+ %th Project
%th Runner
%th Name
- %th Tags
- %th Duration
- %th Finished at
+ %th
%th
- @builds.each do |build|
diff --git a/app/views/admin/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/deploy_keys/new.html.haml b/app/views/admin/deploy_keys/new.html.haml
index 15aa059c93d..5c410a695bf 100644
--- a/app/views/admin/deploy_keys/new.html.haml
+++ b/app/views/admin/deploy_keys/new.html.haml
@@ -14,7 +14,7 @@
.col-sm-10
%p.light
Paste a machine public key here. Read more about how to generate it
- = link_to "here", help_page_path("ssh", "README")
+ = link_to "here", help_page_path("ssh/README")
= f.text_area :key, class: "form-control thin_area", rows: 5
.form-actions
diff --git a/app/views/admin/groups/_group.html.haml b/app/views/admin/groups/_group.html.haml
index 9025aaac097..77a11e49e20 100644
--- a/app/views/admin/groups/_group.html.haml
+++ b/app/views/admin/groups/_group.html.haml
@@ -1,11 +1,9 @@
-- css_class = '' unless local_assigns[:css_class]
-- css_class += ' no-description' if group.description.blank?
+- css_class = 'no-description' if group.description.blank?
%li.group-row{ class: css_class }
- .controls.hidden-xs
- = link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: 'btn btn-grouped btn-sm'
- = link_to 'Destroy', [:admin, group], data: {confirm: "REMOVE #{group.name}? Are you sure?"}, method: :delete, class: 'btn btn-grouped btn-sm btn-remove'
-
+ .controls
+ = link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: 'btn'
+ = link_to 'Delete', [:admin, group], data: { confirm: "Are you sure you want to remove #{group.name}?" }, method: :delete, class: 'btn btn-remove'
.stats
%span
= icon('bookmark')
@@ -18,7 +16,7 @@
%span.visibility-icon.has-tooltip{data: { container: 'body', placement: 'left' }, title: visibility_icon_description(group)}
= visibility_level_icon(group.visibility_level, fw: false)
- = image_tag group_icon(group), class: 'avatar s40 hidden-xs'
+ = image_tag group_icon(group), class: "avatar s40 hidden-xs"
.title
= link_to [:admin, group], class: 'group-name' do
= group.name
diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml
index 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 5b8a0262ea0..bb374694400 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -79,7 +79,7 @@
.panel-body.form-holder
%p.light
Read more about project permissions
- %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
+ %strong= link_to "here", help_page_path("user/permissions"), class: "vlink"
= form_tag members_update_admin_group_path(@group), id: "new_project_member", class: "bulk_import", method: :put do
%div
@@ -88,28 +88,17 @@
= select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2"
%hr
= button_tag 'Add users to group', class: "btn btn-create"
+
+ = render 'shared/members/requests', membership_source: @group, requesters: @requesters
+
.panel.panel-default
.panel-heading
- %h3.panel-title
- Members
- %span.badge
- #{@group.group_members.count}
- %ul.well-list.group-users-list
- - @members.each do |member|
- - user = member.user
- %li{class: dom_class(member), id: (dom_id(user) if user)}
- .list-item-name
- - if user
- %strong
- = link_to user.name, admin_user_path(user)
- - else
- %strong
- = member.invite_email
- (invited)
- %span.pull-right.light
- = member.human_access
- - if can?(current_user, :destroy_group_member, member)
- = link_to group_group_member_path(@group, member), data: { confirm: remove_member_message(member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do
- %i.fa.fa-minus.fa-inverse
+ %strong= @group.name
+ group members
+ %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, as: :member, locals: { show_controls: false }
.panel-footer
= 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/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml
index 7b388cf7862..c217490963f 100644
--- a/app/views/admin/hooks/index.html.haml
+++ b/app/views/admin/hooks/index.html.haml
@@ -3,7 +3,7 @@
System hooks
%p.light
- #{link_to "System hooks ", help_page_path("system_hooks", "system_hooks"), class: "vlink"} can be
+ #{link_to "System hooks ", help_page_path("system_hooks/system_hooks"), class: "vlink"} can be
used for binding events when GitLab creates a User or Project.
%hr
diff --git a/app/views/admin/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..1e755785d90 100644
--- a/app/views/admin/projects/index.html.haml
+++ b/app/views/admin/projects/index.html.haml
@@ -1,97 +1,94 @@
- @no_container = true
- page_title "Projects"
-= render 'shared/show_aside'
+- params[:visibility_level] ||= []
+
= render "admin/dashboard/head"
-%div{ class: (container_class) }
- .row.prepend-top-default
- %aside.col-md-3
- .panel.admin-filter
- = form_tag admin_namespaces_projects_path, method: :get, class: '' do
- .form-group
- = label_tag :name, 'Name:'
- = text_field_tag :name, params[:name], class: "form-control"
+%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
+ - 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 9e55a562e18..b2c607361b3 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -99,7 +99,13 @@
.form-group
= f.label :new_namespace_id, "Namespace", class: 'control-label'
.col-sm-10
- = namespace_select_tag :new_namespace_id, selected: params[:namespace_id], class: 'input-large'
+ .dropdown
+ = dropdown_toggle('Search for Namespace', { toggle: 'dropdown', field_name: 'new_namespace_id', show_any: 'false' }, { toggle_class: 'js-namespace-select large' })
+ .dropdown-menu.dropdown-select
+ = dropdown_title('Namespaces')
+ = dropdown_filter("Search for Namespace")
+ = dropdown_content
+ = dropdown_loading
.form-group
.col-sm-offset-2.col-sm-10
@@ -126,7 +132,7 @@
- else
passed.
- = link_to icon('question-circle'), help_page_path('administration', 'repository_checks')
+ = link_to icon('question-circle'), help_page_path('administration/repository_checks')
.form-group
= f.submit 'Trigger repository check', class: 'btn btn-primary'
@@ -135,44 +141,27 @@
- if @group
.panel.panel-default
.panel-heading
- %strong #{@group.name}
- group members (#{@group.group_members.count})
+ %strong= @group.name
+ group members
+ %span.badge= @group_members.size
.pull-right
= link_to admin_group_path(@group), class: 'btn btn-xs' do
- %i.fa.fa-pencil-square-o
- %ul.well-list
- - @group_members.each do |member|
- = render 'shared/members/member', member: member, show_controls: false
+ = icon('pencil-square-o', text: 'Manage Access')
+ %ul.well-list.content-list
+ = render partial: 'shared/members/member', collection: @group_members, as: :member, locals: { show_controls: false }
.panel-footer
= paginate @group_members, param_name: 'group_members_page', theme: 'gitlab'
+ = render 'shared/members/requests', membership_source: @project, requesters: @requesters
+
.panel.panel-default
.panel-heading
- Project members
- %small
- (#{@project.users.count})
+ %strong= @project.name
+ project members
+ %span.badge= @project.users.size
.pull-right
- = link_to namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-xs" do
- %i.fa.fa-pencil-square-o
- Manage Access
- %ul.well-list.project_members
- - @project_members.each do |project_member|
- - user = project_member.user
- %li.project_member
- .list-item-name
- - if user
- %strong
- = link_to user.name, admin_user_path(user)
- - else
- %strong
- = project_member.invite_email
- (invited)
- .pull-right
- - if project_member.owner?
- %span.light Owner
- - else
- %span.light= project_member.human_access
- = link_to namespace_project_project_member_path(@project.namespace, @project, project_member), data: { confirm: remove_member_message(project_member)}, method: :delete, remote: true, class: "btn btn-sm btn-remove" do
- %i.fa.fa-times
+ = 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, as: :member, locals: { show_controls: false }
.panel-footer
= 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/runners/show.html.haml b/app/views/admin/runners/show.html.haml
index e049b40bfab..61abfc6ecbe 100644
--- a/app/views/admin/runners/show.html.haml
+++ b/app/views/admin/runners/show.html.haml
@@ -28,7 +28,7 @@
.col-md-6
%h4 Restrict projects for this runner
- if @runner.projects.any?
- %table.table
+ %table.table.assigned-projects
%thead
%tr
%th Assigned projects
@@ -44,7 +44,7 @@
.pull-right
= link_to 'Disable', [:admin, project.namespace.becomes(Namespace), project, runner_project], method: :delete, class: 'btn btn-danger btn-xs'
- %table.table
+ %table.table.unassigned-projects
%thead
%tr
%th Project
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..4bf1c9cde3c
--- /dev/null
+++ b/app/views/admin/users/_user.html.haml
@@ -0,0 +1,42 @@
+%li.user-row
+ .user-avatar
+ = image_tag avatar_icon(user), class: "avatar", alt: ''
+ .user-details
+ .user-name
+ = link_to user.name, [:admin, user]
+ - if user.blocked?
+ %span.label.label-danger blocked
+ - if user.admin?
+ %span.label.label-success Admin
+ - if user.external?
+ %span.label.label-default External
+ - if user == current_user
+ %span It's you!
+ .user-email
+ = mail_to user.email, user.email
+ .controls
+ = link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: 'btn'
+ - unless user == current_user
+ .dropdown.inline
+ %a.dropdown-new.btn.btn-default#project-settings-button{href: '#', data: { toggle: 'dropdown' } }
+ = icon('cog')
+ = icon('caret-down')
+ %ul.dropdown-menu.dropdown-menu-align-right
+ %li.dropdown-header
+ Settings
+ %li
+ - if user.ldap_blocked?
+ %span.small Cannot unblock LDAP blocked users
+ - elsif user.blocked?
+ = link_to 'Unblock', unblock_admin_user_path(user), method: :put
+ - else
+ = link_to 'Block', block_admin_user_path(user), data: { confirm: 'USER WILL BE BLOCKED! Are you sure?' }, method: :put
+ - if user.access_locked?
+ %li
+ = link_to 'Unlock', unlock_admin_user_path(user), method: :put, class: 'btn-grouped btn btn-xs btn-success', data: { confirm: 'Are you sure?' }
+ - if user.can_be_removed?
+ %li.divider
+ %li
+ = link_to 'Delete User', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! All issues, merge requests and groups linked to this user will also be removed! Consider cancelling this deletion and blocking the user instead. Are you sure?" },
+ class: 'btn btn-remove btn-block',
+ method: :delete
diff --git a/app/views/admin/users/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'
- &nbsp;
- .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/ci/errors/show.haml b/app/views/ci/errors/show.haml
deleted file mode 100644
index 2788112c835..00000000000
--- a/app/views/ci/errors/show.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-%h3.error Error
-= @error
diff --git a/app/views/ci/shared/_guide.html.haml b/app/views/ci/shared/_guide.html.haml
deleted file mode 100644
index 09e7e653521..00000000000
--- a/app/views/ci/shared/_guide.html.haml
+++ /dev/null
@@ -1,13 +0,0 @@
-.bs-callout.help-callout
- %h4 How to setup CI for this project
-
- %ol
- %li
- Add at least one runner to the project.
- Go to #{link_to 'Runners page', runners_path(@project), target: :blank} for instructions.
- %li
- Put the .gitlab-ci.yml in the root of your repository. Examples can be found in
- #{link_to "Configuring project (.gitlab-ci.yml)", "http://doc.gitlab.com/ci/yaml/README.html", target: :blank}.
- You can also test your .gitlab-ci.yml in the #{link_to "Lint", ci_lint_path}
- %li
- Return to this page and refresh it, it should show a new build.
diff --git a/app/views/ci/shared/_no_runners.html.haml b/app/views/ci/shared/_no_runners.html.haml
deleted file mode 100644
index f56c37d9b37..00000000000
--- a/app/views/ci/shared/_no_runners.html.haml
+++ /dev/null
@@ -1,7 +0,0 @@
-.alert.alert-danger
- %p
- Now you need Runners to process your builds.
- %span
- Checkout the #{link_to 'GitLab Runner section', 'https://about.gitlab.com/gitlab-ci/#gitlab-runner', target: '_blank'} to install it
-
-
diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml
index d35f332e1e0..f7abad54286 100644
--- a/app/views/dashboard/_projects_head.html.haml
+++ b/app/views/dashboard/_projects_head.html.haml
@@ -13,7 +13,7 @@
Explore Projects
.nav-controls
- = form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
+ = form_tag request.path, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
= search_field_tag :filter_projects, params[:filter_projects], placeholder: 'Filter by name...', class: 'project-filter-form-field form-control input-short projects-list-filter', spellcheck: false, id: 'project-filter-form-field', tabindex: "2"
= render 'shared/projects/dropdown'
- if current_user.can_create_project?
diff --git a/app/views/dashboard/projects/_zero_authorized_projects.html.haml b/app/views/dashboard/projects/_zero_authorized_projects.html.haml
index d54c7cad7be..fdea834ff45 100644
--- a/app/views/dashboard/projects/_zero_authorized_projects.html.haml
+++ b/app/views/dashboard/projects/_zero_authorized_projects.html.haml
@@ -1,53 +1,46 @@
- publicish_project_count = ProjectsFinder.new.execute(current_user).count
-%h3.page-title Welcome to GitLab!
-%p.light Self hosted Git management application.
-%hr
-%div
- .dashboard-intro-icon
- %i.fa.fa-bookmark-o
- .dashboard-intro-text
- %p.slead
- You don't have access to any projects right now.
- %br
- - if current_user.can_create_project?
- You can create up to
- %strong= pluralize(number_with_delimiter(current_user.projects_limit), "project") + "."
- - else
- If you are added to a project, it will be displayed here.
-
+.blank-state.blank-state-welcome
+ %h2.blank-state-welcome-title
+ Welcome to GitLab
+ %p.blank-state-text
+ Code, test, and deploy together
+.blank-state
+ .blank-state-icon
+ = custom_icon("project", size: 50)
+ %h3.blank-state-title
+ You don't have access to any projects right now
+ %p.blank-state-text
- if current_user.can_create_project?
- .link_holder
- = link_to new_project_path, class: "btn btn-new" do
- = icon('plus')
- New Project
+ You can create up to
+ %strong= number_with_delimiter(current_user.projects_limit)
+ = succeed "." do
+ = "project".pluralize(current_user.projects_limit)
+ - else
+ If you are added to a project, it will be displayed here.
+ - if current_user.can_create_project?
+ = link_to new_project_path, class: "btn btn-new" do
+ New project
- if current_user.can_create_group?
- %hr
- %div
- .dashboard-intro-icon
- %i.fa.fa-users
- .dashboard-intro-text
- %p.slead
- You can create a group for several dependent projects.
- %br
- Groups are the best way to manage projects and members.
- .link_holder
- = link_to new_group_path, class: "btn btn-new" do
- %i.fa.fa-plus
- New Group
+ .blank-state
+ .blank-state-icon
+ = custom_icon("group", size: 50)
+ %h3.blank-state-title
+ You can create a group for several dependent projects.
+ %p.blank-state-text
+ Groups are the best way to manage projects and members.
+ = link_to new_group_path, class: "btn btn-new" do
+ New group
-if publicish_project_count > 0
- %hr
- %div
- .dashboard-intro-icon
- %i.fa.fa-globe
- .dashboard-intro-text
- %p.slead
- There are
- %strong= number_with_delimiter(publicish_project_count)
- public projects on this server.
- %br
- Public projects are an easy way to allow everyone to have read-only access.
- .link_holder
- = link_to trending_explore_projects_path, class: "btn btn-new" do
- Browse public projects
+ .blank-state
+ .blank-state-icon
+ = icon("globe")
+ %h3.blank-state-title
+ There are
+ = number_with_delimiter(publicish_project_count)
+ public projects on this server.
+ %p.blank-state-text
+ Public projects are an easy way to allow everyone to have read-only access.
+ = link_to trending_explore_projects_path, class: "btn btn-new" do
+ Browse projects
diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml
index 4565e752c1f..4f36a4a1c73 100644
--- a/app/views/dashboard/projects/index.html.haml
+++ b/app/views/dashboard/projects/index.html.haml
@@ -5,7 +5,8 @@
- page_title "Projects"
- header_title "Projects", dashboard_projects_path
-= render 'dashboard/projects_head'
+- if @projects.any? || params[:filter_projects]
+ = render 'dashboard/projects_head'
- if @last_push
= render "events/event_last_push", event: @last_push
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index fc42e5dcc66..4e340b6ec16 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -9,14 +9,14 @@
%span
To do
%span.badge
- = todos_pending_count
+ = number_with_delimiter(todos_pending_count)
- todo_done_active = ('active' if params[:state] == 'done')
%li{class: "todos-done #{todo_done_active}"}
= link_to todos_filter_path(state: 'done') do
%span
Done
%span.badge
- = todos_done_count
+ = number_with_delimiter(todos_done_count)
.nav-controls
- if @todos.any?(&:pending?)
diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml
index a373f61bd3c..4debd3d608f 100644
--- a/app/views/devise/sessions/two_factor.html.haml
+++ b/app/views/devise/sessions/two_factor.html.haml
@@ -1,3 +1,7 @@
+- if inject_u2f_api?
+ - content_for :page_specific_javascripts do
+ = page_specific_javascript_tag('u2f.js')
+
%div
.login-box
.login-heading
diff --git a/app/views/emojis/index.html.haml b/app/views/emojis/index.html.haml
index 97401a2e618..790d90ad3ee 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: "Search emoji"
.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/errors/access_denied.html.haml b/app/views/errors/access_denied.html.haml
index 012e9857642..c034bbe430e 100644
--- a/app/views/errors/access_denied.html.haml
+++ b/app/views/errors/access_denied.html.haml
@@ -3,4 +3,4 @@
%h3 Access Denied
%hr
%p You are not allowed to access this page.
-%p Read more about project permissions #{link_to "here", help_page_path("permissions", "permissions"), class: "vlink"}
+%p Read more about project permissions #{link_to "here", help_page_path("user/permissions"), class: "vlink"}
diff --git a/app/views/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/_new_group_member.html.haml b/app/views/groups/group_members/_new_group_member.html.haml
index e7ab4f2409b..9bb9f962177 100644
--- a/app/views/groups/group_members/_new_group_member.html.haml
+++ b/app/views/groups/group_members/_new_group_member.html.haml
@@ -12,7 +12,7 @@
= select_tag :access_level, options_for_select(GroupMember.access_level_roles, @group_member.access_level), class: "project-access-select select2"
.help-block
Read more about role permissions
- %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
+ %strong= link_to "here", help_page_path("user/permissions"), class: "vlink"
.form-actions
= f.submit 'Add users to group', class: "btn btn-create"
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index a36531e095a..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,14 +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
- %small
- (#{@members.total_count})
+ %span.badge= @members.size
.controls
= form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do
.form-group
@@ -26,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/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml
index 121a7de3ad7..a8fdbd8c426 100644
--- a/app/views/groups/milestones/index.html.haml
+++ b/app/views/groups/milestones/index.html.haml
@@ -6,7 +6,6 @@
.nav-controls
- if can?(current_user, :admin_milestones, @group)
= link_to new_group_milestone_path(@group), class: "btn btn-new" do
- = icon('plus')
New Milestone
.row-content-block
diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml
index c2f2d9912f7..33fee334d93 100644
--- a/app/views/groups/projects.html.haml
+++ b/app/views/groups/projects.html.haml
@@ -7,7 +7,6 @@
- if can? current_user, :admin_group, @group
.controls
= link_to new_project_path(namespace_id: @group.id), class: "btn btn-sm btn-success" do
- = icon('plus')
New Project
%ul.well-list
- @projects.each do |project|
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 62ebd69485c..eddeae98bc4 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -5,9 +5,8 @@
= 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) }
- = link_to group_icon(@group), target: '_blank' do
- = image_tag group_icon(@group), class: "avatar group-avatar s70"
+ %div{ class: container_class }
+ = image_tag group_icon(@group), class: "avatar group-avatar s70"
.group-info
.cover-title
%h1
@@ -15,13 +14,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
@@ -33,7 +34,7 @@
= link_to "#shared", 'data-toggle' => 'tab' do
Shared Projects
.nav-controls
- = form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
+ = form_tag request.path, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false
= render 'shared/projects/dropdown'
- if can? current_user, :create_projects, @group
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index 01648047ce2..ce4536ebdc6 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -20,6 +20,10 @@
%td Focus Search
%tr
%td.shortcut
+ .key f
+ %td Focus Filter
+ %tr
+ %td.shortcut
.key ?
%td Show/hide this dialog
%tr
@@ -28,8 +32,12 @@
.key &#8984; shift p
- else
.key ctrl shift p
-
%td Toggle Markdown preview
+ %tr
+ %td.shortcut
+ .key
+ %i.fa.fa-arrow-up
+ %td Edit last comment (when focused on an empty textarea)
%tbody
%tr
%th
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/help/show.html.haml b/app/views/help/show.html.haml
index 0398afb4c1d..be257b51b9e 100644
--- a/app/views/help/show.html.haml
+++ b/app/views/help/show.html.haml
@@ -1,3 +1,3 @@
-- page_title @file.humanize, *@category.split("/").reverse.map(&:humanize)
+- page_title @path.split("/").reverse.map(&:humanize)
.documentation.wiki
= markdown @markdown.gsub('$your_email', current_user.try(:email) || "email@example.com")
diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml
index d676bc28c89..431d312b4ca 100644
--- a/app/views/help/ui.html.haml
+++ b/app/views/help/ui.html.haml
@@ -549,4 +549,4 @@
%li wiki page
%li help page
- You can check how markdown rendered at #{link_to 'Markdown help page', help_page_path("markdown", "markdown")}.
+ You can check how markdown rendered at #{link_to 'Markdown help page', help_page_path("markdown/markdown")}.
diff --git a/app/views/import/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..4c6af0b7908
--- /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/_collapse_button.html.haml b/app/views/layouts/_collapse_button.html.haml
deleted file mode 100644
index 8c140a5943e..00000000000
--- a/app/views/layouts/_collapse_button.html.haml
+++ /dev/null
@@ -1,3 +0,0 @@
-= link_to '#', class: 'nav-header-btn text-center toggle-nav-collapse', title: "Open/Close" do
- %span.sr-only Toggle navigation
- = icon('bars')
diff --git a/app/views/layouts/_flash.html.haml b/app/views/layouts/_flash.html.haml
index cc8ea066cb9..3612f1ce5c6 100644
--- a/app/views/layouts/_flash.html.haml
+++ b/app/views/layouts/_flash.html.haml
@@ -1,4 +1,4 @@
-.flash-container
+.flash-container.flash-container-page
- if alert
.flash-alert
= alert
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index e0ed657919e..757de92d6d4 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -30,8 +30,8 @@
= javascript_include_tag "application"
- - if page_specific_javascripts
- = javascript_include_tag page_specific_javascripts, {"data-turbolinks-track" => true}
+ - if content_for?(:page_specific_javascripts)
+ = yield :page_specific_javascripts
= csrf_meta_tags
diff --git a/app/views/layouts/_init_auto_complete.html.haml b/app/views/layouts/_init_auto_complete.html.haml
index 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 199ab3c38c3..a1a71c2fb33 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -1,6 +1,13 @@
.page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" }
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
- = render partial: 'layouts/collapse_button'
+ .sidebar-action-buttons
+ = link_to '#', class: 'nav-header-btn toggle-nav-collapse', title: "Open/Close" do
+ %span.sr-only Toggle navigation
+ = icon('bars')
+ = link_to '#', class: "nav-header-btn pin-nav-btn has-tooltip #{'is-active' if pinned_nav?} js-nav-pin", title: pinned_nav? ? "Unpin navigation" : "Pin Navigation", data: {placement: 'right', container: 'body'} do
+ %span.sr-only Toggle navigation pinning
+ = icon('fw thumb-tack')
+
- if defined?(sidebar) && sidebar
= render "layouts/nav/#{sidebar}"
- elsif current_user
@@ -8,14 +15,6 @@
- else
= render 'layouts/nav/explore'
- - if current_user
- = link_to current_user, class: 'sidebar-user', title: "Profile", data: {user: current_user.username} do
- = image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36'
- .username
- = current_user.username
- = link_to '#', class: "nav-header-btn text-center pin-nav-btn #{'is-active' if pinned_nav?} js-nav-pin", title: 'Pin/Unpin navigation' do
- %span.sr-only Toggle navigation pinning
- = icon('thumb-tack')
- if defined?(nav) && nav
.layout-nav
.container-fluid
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index 245b9c3b4d4..f7580f00159 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -44,7 +44,7 @@
name: "#{j(@project.name)}"
};
- - if @group and @group.path
+ - if @group && @group.persisted? && @group.path
:javascript
gl.groupOptions = gl.groupOptions || {};
gl.groupOptions["#{j(@group.path)}"] = {
diff --git a/app/views/layouts/ci/_info.html.haml b/app/views/layouts/ci/_info.html.haml
deleted file mode 100644
index 24c68a6dbf5..00000000000
--- a/app/views/layouts/ci/_info.html.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-- if current_user && current_user.is_admin? && Ci::Runner.count.zero?
- = render 'ci/shared/no_runners'
diff --git a/app/views/layouts/ci/_page.html.haml b/app/views/layouts/ci/_page.html.haml
deleted file mode 100644
index 2e56d0ac6a3..00000000000
--- a/app/views/layouts/ci/_page.html.haml
+++ /dev/null
@@ -1,22 +0,0 @@
-.page-with-sidebar{ class: page_sidebar_class }
- = render "layouts/broadcast"
- .sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
-
- - if defined?(sidebar) && sidebar
- = render "layouts/ci/#{sidebar}"
- - elsif current_user
- = render 'layouts/nav/dashboard'
- .collapse-nav
- = render partial: 'layouts/collapse_button'
- - if current_user
- = link_to current_user, class: 'sidebar-user', title: "Profile" do
- = image_tag avatar_icon(current_user, 60), alt: 'Profile', class: 'avatar avatar s36'
- .username
- = current_user.username
- .content-wrapper
- = render "layouts/flash"
- = render 'layouts/ci/info'
- %div{ class: container_class }
- .content
- .clearfix
- = yield
diff --git a/app/views/layouts/ci/notify.html.haml b/app/views/layouts/ci/notify.html.haml
deleted file mode 100644
index 270b206df5e..00000000000
--- a/app/views/layouts/ci/notify.html.haml
+++ /dev/null
@@ -1,19 +0,0 @@
-%html{lang: "en"}
- %head
- %meta{content: "text/html; charset=utf-8", "http-equiv" => "Content-Type"}
- %title
- GitLab CI
-
- %body
- = yield :header
-
- %table{align: "left", border: "0", cellpadding: "0", cellspacing: "0", style: "padding: 10px 0;", width: "100%"}
- %tr
- %td{align: "left", style: "margin: 0; padding: 10px;"}
- = yield
- %br
- %tr
- %td{align: "left", style: "margin: 0; padding: 10px;"}
- %p{style: "font-size:small;color:#777"}
- - if @project
- You're receiving this notification because you are the one who triggered a build on the #{@project.name} project.
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 40a2c81eebd..94c53882623 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -1,46 +1,56 @@
%header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class }
%div{ class: fluid_layout ? "container-fluid" : "container-fluid" }
.header-content
- %button.side-nav-toggle{type: 'button'}
+ %button.side-nav-toggle{ type: 'button', "aria-label" => "Toggle global navigation" }
%span.sr-only Toggle navigation
= icon('bars')
%button.navbar-toggle{type: 'button'}
%span.sr-only Toggle navigation
- = icon('angle-left')
+ = icon('ellipsis-v')
.navbar-collapse.collapse
%ul.nav.navbar-nav
%li.hidden-sm.hidden-xs
= render 'layouts/search' unless current_controller?(:search)
%li.visible-sm.visible-xs
- = link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = link_to search_path, title: 'Search', aria: { label: "Search" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('search')
- if current_user
- if session[:impersonator_id]
%li.impersonation
- = link_to admin_impersonation_path, method: :delete, title: 'Stop Impersonation', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
+ = link_to admin_impersonation_path, method: :delete, title: "Stop Impersonation", aria: { label: 'Stop Impersonation' }, data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
= icon('user-secret fw')
- if current_user.is_admin?
%li
- = link_to admin_root_path, title: 'Admin Area', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = link_to admin_root_path, title: 'Admin Area', aria: { label: "Admin Area" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('wrench fw')
%li
- = link_to dashboard_todos_path, title: 'Todos', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = link_to dashboard_todos_path, title: 'Todos', aria: { label: "Todos" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('bell fw')
%span.badge.todos-pending-count{ class: ("hidden" if todos_pending_count == 0) }
= todos_pending_count
- if current_user.can_create_project?
%li
- = link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = link_to new_project_path, title: 'New project', aria: { label: "New project" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('plus fw')
- if Gitlab::Sherlock.enabled?
%li
= link_to sherlock_transactions_path, title: 'Sherlock Transactions',
data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('tachometer fw')
- %li
- = link_to destroy_user_session_path, class: 'logout', method: :delete, title: 'Sign out', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
- = icon('sign-out')
+ %li.header-user.dropdown
+ = link_to current_user, class: "header-user-dropdown-toggle", data: { toggle: "dropdown" } do
+ = image_tag avatar_icon(current_user, 26), width: 26, height: 26, class: "header-user-avatar"
+ %span.caret
+ .dropdown-menu-nav.dropdown-menu-align-right
+ %ul
+ %li
+ = link_to "Profile", current_user, class: 'profile-link', aria: { label: "Profile" }, data: { user: current_user.username }
+ %li
+ = link_to "Profile Settings", profile_path, aria: { label: "Profile Settings" }
+ %li.divider
+ %li
+ = link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link", aria: { label: "Sign out" }
- else
%li
%div
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
index f02ac949699..5ee8772882e 100644
--- a/app/views/layouts/nav/_admin.html.haml
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -1,60 +1,40 @@
-%ul.nav-links.scrolling-tabs
+.scrolling-tabs-container{ class: nav_control_class }
+ = render 'layouts/nav/admin_settings'
.fade-left
- = nav_link(controller: %w(dashboard admin projects users groups builds runners), html_options: {class: 'home'}) do
- = link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do
- %span
- Overview
- = nav_link(controller: %w(background_jobs logs health_check)) do
- = link_to admin_background_jobs_path, title: 'Monitoring' do
- %span
- Monitoring
- = nav_link(controller: :deploy_keys) do
- = link_to admin_deploy_keys_path, title: 'Deploy Keys' do
- %span
- Deploy Keys
- = nav_link(controller: :broadcast_messages) do
- = link_to admin_broadcast_messages_path, title: 'Messages' do
- %span
- Messages
- = nav_link(controller: :hooks) do
- = link_to admin_hooks_path, title: 'Hooks' do
- %span
- Hooks
-
- = nav_link(controller: :appearances) do
- = link_to admin_appearances_path, title: 'Appearances' do
- %span
- Appearance
-
- = nav_link(controller: :applications) do
- = link_to admin_applications_path, title: 'Applications' do
- %span
- Applications
-
- = nav_link(controller: :services) do
- = link_to admin_application_settings_services_path, title: 'Service Templates' do
- %span
- Service Templates
-
- = nav_link(controller: :labels) do
- = link_to admin_labels_path, title: 'Labels' do
- %span
- Labels
+ = icon('angle-left')
+ .fade-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(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
+ = link_to admin_broadcast_messages_path, title: 'Messages' do
+ %span
+ Messages
+ = nav_link(controller: :hooks) do
+ = link_to admin_hooks_path, title: 'Hooks' do
+ %span
+ System Hooks
- = nav_link(controller: :abuse_reports) do
- = link_to admin_abuse_reports_path, title: "Abuse Reports" do
- %span
- Abuse Reports
- %span.badge.count= number_with_delimiter(AbuseReport.count(:all))
+ = nav_link(controller: :applications) do
+ = link_to admin_applications_path, title: 'Applications' do
+ %span
+ Applications
- - if askimet_enabled?
- = nav_link(controller: :spam_logs) do
- = link_to admin_spam_logs_path, title: "Spam Logs" do
+ = nav_link(controller: :abuse_reports) do
+ = link_to admin_abuse_reports_path, title: "Abuse Reports" do
%span
- Spam Logs
+ Abuse Reports
+ %span.badge.count= number_with_delimiter(AbuseReport.count(:all))
- = nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do
- = link_to admin_application_settings_path, title: 'Settings' do
- %span
- Settings
- .fade-right
+ - if askimet_enabled?
+ = nav_link(controller: :spam_logs) do
+ = link_to admin_spam_logs_path, title: "Spam Logs" do
+ %span
+ Spam Logs
diff --git a/app/views/layouts/nav/_admin_settings.html.haml b/app/views/layouts/nav/_admin_settings.html.haml
new file mode 100644
index 00000000000..38e9b80d129
--- /dev/null
+++ b/app/views/layouts/nav/_admin_settings.html.haml
@@ -0,0 +1,31 @@
+.controls
+ .dropdown.admin-settings-dropdown
+ %a.dropdown-new.btn.btn-default{href: '#', 'data-toggle' => 'dropdown'}
+ = icon('cog')
+ = icon('caret-down')
+ %ul.dropdown-menu.dropdown-menu-align-right
+ = nav_link(controller: :deploy_keys) do
+ = link_to admin_deploy_keys_path, title: 'Deploy Keys' do
+ %span
+ Deploy Keys
+
+ = nav_link(controller: :services) do
+ = link_to admin_application_settings_services_path, title: 'Service Templates' do
+ %span
+ Service Templates
+
+ = nav_link(controller: :labels) do
+ = link_to admin_labels_path, title: 'Labels' do
+ %span
+ Labels
+
+ = nav_link(controller: :appearances) do
+ = link_to admin_appearances_path, title: 'Appearances' do
+ %span
+ Appearance
+
+ %li.divider
+ = nav_link(controller: :application_settings) do
+ = link_to admin_application_settings_path, title: 'Settings' do
+ %span
+ Settings
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 52e41b1a857..21668698814 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -1,64 +1,44 @@
%ul.nav.nav-sidebar
= nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do
= link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
- .icon-container
- = navbar_icon('project')
%span
Projects
= nav_link(controller: :todos) do
= link_to dashboard_todos_path, title: 'Todos' do
- .icon-container
- = icon('bell fw')
%span
Todos
%span.count= number_with_delimiter(todos_pending_count)
= nav_link(path: 'dashboard#activity') do
= link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do
- .icon-container
- = navbar_icon('activity')
%span
Activity
= nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
= link_to dashboard_groups_path, title: 'Groups' do
- .icon-container
- = navbar_icon('group')
%span
Groups
= nav_link(controller: 'dashboard/milestones') do
= link_to dashboard_milestones_path, title: 'Milestones' do
- .icon-container
- = navbar_icon('milestones')
%span
Milestones
= nav_link(path: 'dashboard#issues') do
= link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues' do
- .icon-container
- = navbar_icon('issues')
%span
Issues
%span.count= number_with_delimiter(current_user.assigned_issues.opened.count)
= nav_link(path: 'dashboard#merge_requests') do
= link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do
- .icon-container
- = navbar_icon('mr')
%span
Merge Requests
%span.count= number_with_delimiter(current_user.assigned_merge_requests.opened.count)
= nav_link(controller: :snippets) do
= link_to dashboard_snippets_path, title: 'Snippets' do
- .icon-container
- = icon('clipboard fw')
%span
Snippets
= nav_link(controller: :help) do
= link_to help_path, title: 'Help' do
- .icon-container
- = icon('question-circle fw')
%span
Help
= nav_link(html_options: {class: profile_tab_class}) do
= link_to profile_path, title: 'Profile Settings', data: {placement: 'bottom'} do
- .icon-container
- = icon('user fw')
%span
Profile Settings
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index 66361a644dd..d7d36c84b6c 100644
--- a/app/views/layouts/nav/_group.html.haml
+++ b/app/views/layouts/nav/_group.html.haml
@@ -1,8 +1,10 @@
-%div{ class: nav_control_class }
+.scrolling-tabs-container{ class: nav_control_class }
= render 'layouts/nav/group_settings'
-
+ .fade-left
+ = icon('angle-left')
+ .fade-right
+ = icon('angle-right')
%ul.nav-links.scrolling-tabs
- .fade-left
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do
= link_to group_path(@group), title: 'Home' do
%span
@@ -31,4 +33,3 @@
= link_to group_group_members_path(@group), title: 'Members' do
%span
Members
- .fade-right
diff --git a/app/views/layouts/nav/_group_settings.html.haml b/app/views/layouts/nav/_group_settings.html.haml
index dac46648b9f..bf9a7ecb786 100644
--- a/app/views/layouts/nav/_group_settings.html.haml
+++ b/app/views/layouts/nav/_group_settings.html.haml
@@ -1,16 +1,22 @@
- if current_user
- - if access = @group.users.find_by(id: current_user.id)
- .controls
- .dropdown.group-settings-dropdown
- %a.dropdown-new.btn.btn-default#group-settings-button{href: '#', 'data-toggle' => 'dropdown'}
- = icon('cog')
- = icon('caret-down')
- %ul.dropdown-menu.dropdown-menu-align-right
- - if can?(current_user, :admin_group, @group)
- = nav_link(path: 'groups#projects') do
- = link_to projects_group_path(@group), title: 'Projects' do
- Projects
- %li.divider
- %li
- = link_to edit_group_path(@group) do
- Edit Group
+ - can_edit = can?(current_user, :admin_group, @group)
+ - member = @group.members.find_by(user_id: current_user.id)
+ - can_leave = member && can?(current_user, :destroy_group_member, member)
+
+ .controls
+ .dropdown.group-settings-dropdown
+ %a.dropdown-new.btn.btn-default#group-settings-button{href: '#', 'data-toggle' => 'dropdown'}
+ = icon('cog')
+ = icon('caret-down')
+ %ul.dropdown-menu.dropdown-menu-align-right
+ = nav_link(path: 'groups#projects') do
+ = link_to 'Projects', projects_group_path(@group), title: 'Projects'
+ %li.divider
+ - if can_edit
+ %li
+ = link_to 'Edit Group', edit_group_path(@group)
+ - if can_leave
+ %li
+ = link_to polymorphic_path([:leave, @group, :members]),
+ data: { confirm: leave_confirmation_message(@group) }, method: :delete, title: 'Leave group' do
+ Leave Group
diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml
index bb6f14a6225..6d514f669db 100644
--- a/app/views/layouts/nav/_profile.html.haml
+++ b/app/views/layouts/nav/_profile.html.haml
@@ -1,46 +1,49 @@
-%ul.nav-links.scrolling-tabs
+.scrolling-tabs-container
.fade-left
- = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
- = link_to profile_path, title: 'Profile Settings' do
- %span
- Profile
- = nav_link(controller: [:accounts, :two_factor_auths]) do
- = link_to profile_account_path, title: 'Account' do
- %span
- Account
- - if current_application_settings.user_oauth_applications?
- = nav_link(controller: 'oauth/applications') do
- = link_to applications_profile_path, title: 'Applications' do
- %span
- Applications
- = nav_link(controller: :personal_access_tokens) do
- = link_to profile_personal_access_tokens_path, title: 'Personal Access Tokens' do
- %span
- Personal Access Tokens
- = nav_link(controller: :emails) do
- = link_to profile_emails_path, title: 'Emails' do
- %span
- Emails
- - unless current_user.ldap_user?
- = nav_link(controller: :passwords) do
- = link_to edit_profile_password_path, title: 'Password' do
- %span
- Password
- = nav_link(controller: :notifications) do
- = link_to profile_notifications_path, title: 'Notifications' do
- %span
- Notifications
-
- = nav_link(controller: :keys) do
- = link_to profile_keys_path, title: 'SSH Keys' do
- %span
- SSH Keys
- = nav_link(controller: :preferences) do
- = link_to profile_preferences_path, title: 'Preferences' do
- %span
- Preferences
- = nav_link(path: 'profiles#audit_log') do
- = link_to audit_log_profile_path, title: 'Audit Log' do
- %span
- Audit Log
+ = icon('angle-left')
.fade-right
+ = icon('angle-right')
+ %ul.nav-links.scrolling-tabs
+ = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
+ = link_to profile_path, title: 'Profile Settings' do
+ %span
+ Profile
+ = nav_link(controller: [:accounts, :two_factor_auths]) do
+ = link_to profile_account_path, title: 'Account' do
+ %span
+ Account
+ - if current_application_settings.user_oauth_applications?
+ = nav_link(controller: 'oauth/applications') do
+ = link_to applications_profile_path, title: 'Applications' do
+ %span
+ Applications
+ = nav_link(controller: :personal_access_tokens) do
+ = link_to profile_personal_access_tokens_path, title: 'Access Tokens' do
+ %span
+ Access Tokens
+ = nav_link(controller: :emails) do
+ = link_to profile_emails_path, title: 'Emails' do
+ %span
+ Emails
+ - unless current_user.ldap_user?
+ = nav_link(controller: :passwords) do
+ = link_to edit_profile_password_path, title: 'Password' do
+ %span
+ Password
+ = nav_link(controller: :notifications) do
+ = link_to profile_notifications_path, title: 'Notifications' do
+ %span
+ Notifications
+
+ = nav_link(controller: :keys) do
+ = link_to profile_keys_path, title: 'SSH Keys' do
+ %span
+ SSH Keys
+ = nav_link(controller: :preferences) do
+ = link_to profile_preferences_path, title: 'Preferences' do
+ %span
+ Preferences
+ = nav_link(path: 'profiles#audit_log') do
+ = link_to audit_log_profile_path, title: 'Audit Log' do
+ %span
+ Audit Log
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 39ea4920ccc..9e65d94186b 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -5,27 +5,31 @@
= icon('cog')
= icon('caret-down')
%ul.dropdown-menu.dropdown-menu-align-right
- - is_project_member = @project.users.exists?(current_user.id)
- - access = @project.team.max_member_access(current_user.id)
- can_edit = can?(current_user, :admin_project, @project)
+ -# We don't use @project.team.find_member because it searches for group members too...
+ - member = @project.members.find_by(user_id: current_user.id)
+ - can_leave = member && can?(current_user, :destroy_project_member, member)
- = render 'layouts/nav/project_settings', access: access, can_edit: can_edit
+ = render 'layouts/nav/project_settings', can_edit: can_edit
- - if can_edit || is_project_member
+ - if can_edit || can_leave
%li.divider
- if can_edit
%li
= link_to edit_project_path(@project) do
Edit Project
- - if is_project_member
+ - if can_leave
%li
= link_to polymorphic_path([:leave, @project, :members]),
data: { confirm: leave_confirmation_message(@project) }, method: :delete, title: 'Leave project' do
Leave Project
-%div{ class: nav_control_class }
+.scrolling-tabs-container{ class: nav_control_class }
+ .fade-left
+ = icon('angle-left')
+ .fade-right
+ = icon('angle-right')
%ul.nav-links.scrolling-tabs
- .fade-left
= nav_link(path: 'projects#show', html_options: {class: 'home'}) do
= link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do
%span
@@ -38,9 +42,9 @@
- if project_nav_tab? :files
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file commit commits compare repositories tags branches releases network)) do
- = link_to project_files_path(@project), title: 'Code', class: 'shortcuts-tree' do
+ = link_to project_files_path(@project), title: 'Repository', class: 'shortcuts-tree' do
%span
- Code
+ Repository
- if project_nav_tab? :pipelines
= nav_link(controller: [:pipelines, :builds, :environments]) do
@@ -109,4 +113,3 @@
%li.hidden
= link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do
Commits
- .fade-right
diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml
index 13d32bd1354..51a54b4f262 100644
--- a/app/views/layouts/nav/_project_settings.html.haml
+++ b/app/views/layouts/nav/_project_settings.html.haml
@@ -3,7 +3,7 @@
= link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do
%span
Members
-- if access && can_edit
+- if can_edit
- if @project.allowed_to_share_with_group?
= nav_link(controller: :group_links) do
= link_to namespace_project_group_links_path(@project.namespace, @project), title: "Groups" do
diff --git a/app/views/notify/note_merge_request_email.html.haml b/app/views/notify/note_merge_request_email.html.haml
index a3643a00cfe..ea7e3d199fd 100644
--- a/app/views/notify/note_merge_request_email.html.haml
+++ b/app/views/notify/note_merge_request_email.html.haml
@@ -1,7 +1,7 @@
-- if @note.legacy_diff_note?
+- if @note.diff_note? && @note.diff_file
%p.details
New comment on diff for
- = link_to @note.diff_file_path, @target_url
+ = link_to @note.diff_file.file_path, @target_url
\:
= render 'note_message'
diff --git a/app/views/notify/project_was_not_exported_email.html.haml b/app/views/notify/project_was_not_exported_email.html.haml
index c9e9ade2cf1..c888da29c17 100644
--- a/app/views/notify/project_was_not_exported_email.html.haml
+++ b/app/views/notify/project_was_not_exported_email.html.haml
@@ -6,4 +6,4 @@
%ul
- @errors.each do |error|
%li
- error
+ #{error}
diff --git a/app/views/notify/project_was_not_exported_email.text.erb b/app/views/notify/project_was_not_exported_email.text.erb
deleted file mode 100644
index a07f6edacf7..00000000000
--- a/app/views/notify/project_was_not_exported_email.text.erb
+++ /dev/null
@@ -1,6 +0,0 @@
-Project <%= @project.name %> couldn't be exported.
-
-The errors we encountered were:
-
-- @errors.each do |error|
-<%= error %> \ No newline at end of file
diff --git a/app/views/notify/project_was_not_exported_email.text.haml b/app/views/notify/project_was_not_exported_email.text.haml
new file mode 100644
index 00000000000..b27cb620b9e
--- /dev/null
+++ b/app/views/notify/project_was_not_exported_email.text.haml
@@ -0,0 +1,6 @@
+= "Project #{@project.name} couldn't be exported."
+
+= "The errors we encountered were:"
+
+- @errors.each do |error|
+ #{error} \ No newline at end of file
diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml
index f1532371b2e..c161ecc3463 100644
--- a/app/views/notify/repository_push_email.html.haml
+++ b/app/views/notify/repository_push_email.html.haml
@@ -72,12 +72,11 @@
The diff for this file was not included because it is too large.
- else
%hr
- - diff_commit = diff_file.deleted_file ? @message.diff_refs.first : @message.diff_refs.last
- - blob = @message.project.repository.blob_for_diff(diff_commit, diff_file)
+ - blob = diff_file.blob
- if blob && blob.respond_to?(:text?) && blob_text_viewable?(blob)
%table.code.white
- diff_file.highlighted_diff_lines.each do |line|
- = render "projects/diffs/line", {line: line, diff_file: diff_file, line_code: nil, plain: true}
+ = render "projects/diffs/line", line: line, diff_file: diff_file, plain: true
- else
No preview for this file type
%br
diff --git a/app/views/profiles/_head.html.haml b/app/views/profiles/_head.html.haml
new file mode 100644
index 00000000000..003884a5bd9
--- /dev/null
+++ b/app/views/profiles/_head.html.haml
@@ -0,0 +1,3 @@
+- content_for :page_specific_javascripts do
+ = page_specific_javascript_tag('lib/cropper.js')
+ = page_specific_javascript_tag('profile/application.js')
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index 8efe486e01b..57d16d29158 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -1,4 +1,5 @@
- page_title "Account"
+= render 'profiles/head'
- if current_user.ldap_user?
.alert.alert-info
diff --git a/app/views/profiles/audit_log.html.haml b/app/views/profiles/audit_log.html.haml
index 9c404b6935f..9fe86e6b291 100644
--- a/app/views/profiles/audit_log.html.haml
+++ b/app/views/profiles/audit_log.html.haml
@@ -1,4 +1,5 @@
- page_title "Audit Log"
+= render 'profiles/head'
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml
index 6f7fefdb46d..dc499be885b 100644
--- a/app/views/profiles/emails/index.html.haml
+++ b/app/views/profiles/emails/index.html.haml
@@ -1,4 +1,5 @@
- page_title "Emails"
+= render 'profiles/head'
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
diff --git a/app/views/profiles/keys/_form.html.haml b/app/views/profiles/keys/_form.html.haml
index b3ed59a1a4a..6ea358d9f63 100644
--- a/app/views/profiles/keys/_form.html.haml
+++ b/app/views/profiles/keys/_form.html.haml
@@ -4,7 +4,7 @@
.form-group
= f.label :key, class: 'label-light'
- = f.text_area :key, class: "form-control", rows: 8, required: true
+ = f.text_area :key, class: "form-control", rows: 8, required: true, placeholder: "Don't paste the private part of the SSH key. Paste the public part, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'."
.form-group
= f.label :title, class: 'label-light'
= f.text_field :title, class: "form-control", required: true
diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml
index 6a067a03535..a42b3b8eb38 100644
--- a/app/views/profiles/keys/index.html.haml
+++ b/app/views/profiles/keys/index.html.haml
@@ -11,7 +11,7 @@
Add an SSH key
%p.profile-settings-content
Before you can add an SSH key you need to
- = link_to "generate it.", help_page_path("ssh", "README")
+ = link_to "generate it.", help_page_path("ssh/README")
= render 'form'
%hr
%h5
diff --git a/app/views/profiles/keys/show.html.haml b/app/views/profiles/keys/show.html.haml
index 89f6f01581a..6283ceebf10 100644
--- a/app/views/profiles/keys/show.html.haml
+++ b/app/views/profiles/keys/show.html.haml
@@ -1,2 +1,3 @@
- page_title @key.title, "SSH Keys"
+= render 'profiles/head'
= render "key_details"
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index 5afd83a522e..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?
@@ -28,7 +29,7 @@
= label_tag :global_notification_level, "Global notification level", class: "label-light"
%br
.clearfix
- .form-group.pull-left
+ .form-group.pull-left.global-notification-setting
= render 'shared/notifications/button', notification_setting: @global_notification_setting, left_align: true
.clearfix
diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml
index 1b45548bd02..71ac367830d 100644
--- a/app/views/profiles/personal_access_tokens/index.html.haml
+++ b/app/views/profiles/personal_access_tokens/index.html.haml
@@ -1,4 +1,5 @@
- page_title "Personal Access Tokens"
+= render 'profiles/head'
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index 1b1b16d656f..2afa026847a 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -1,4 +1,5 @@
- page_title 'Preferences'
+= render 'profiles/head'
= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: {class: 'row prepend-top-default js-preferences-form'} do |f|
.col-lg-3.profile-settings-sidebar
@@ -42,12 +43,12 @@
.form-group
= f.label :dashboard, class: 'label-light' do
Default Dashboard
- = link_to('(?)', help_page_path('profile', 'preferences') + '#default-dashboard', target: '_blank')
+ = link_to('(?)', help_page_path('profile/preferences') + '#default-dashboard', target: '_blank')
= f.select :dashboard, dashboard_choices, {}, class: 'form-control'
.form-group
= f.label :project_view, class: 'label-light' do
Project view
- = link_to('(?)', help_page_path('profile', 'preferences') + '#default-project-view', target: '_blank')
+ = link_to('(?)', help_page_path('profile/preferences') + '#default-project-view', target: '_blank')
= f.select :project_view, project_view_choices, {}, class: 'form-control'
.help-block
Choose what content you want to see on a project's home page.
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index eef50d887c7..d9fa74fad90 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -1,3 +1,5 @@
+= render 'profiles/head'
+
= form_for @user, url: profile_path, method: :put, html: { multipart: true, class: "edit-user prepend-top-default" }, authenticity_token: true do |f|
= form_errors(@user)
diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml
index 593be2617c1..366f1fed35b 100644
--- a/app/views/profiles/two_factor_auths/show.html.haml
+++ b/app/views/profiles/two_factor_auths/show.html.haml
@@ -1,5 +1,10 @@
- page_title 'Two-Factor Authentication', 'Account'
- header_title "Two-Factor Authentication", profile_two_factor_auth_path
+= render 'profiles/head'
+
+- if inject_u2f_api?
+ - content_for :page_specific_javascripts do
+ = page_specific_javascript_tag('u2f.js')
.row.prepend-top-default
.col-lg-3
@@ -13,7 +18,7 @@
- else
%p
Download the Google Authenticator application from App Store or Google Play Store and scan this code.
- More information is available in the #{link_to('documentation', help_page_path('profile', 'two_factor_authentication'))}.
+ More information is available in the #{link_to('documentation', help_page_path('profile/two_factor_authentication'))}.
.row.append-bottom-10
.col-md-3
= raw @qr_code
diff --git a/app/views/projects/_bitbucket_import_modal.html.haml b/app/views/projects/_bitbucket_import_modal.html.haml
index 2987f6b5b22..e74fd5b93ea 100644
--- a/app/views/projects/_bitbucket_import_modal.html.haml
+++ b/app/views/projects/_bitbucket_import_modal.html.haml
@@ -10,4 +10,4 @@
as administrator you need to configure
- else
ask your GitLab administrator to configure
- == #{link_to 'OAuth integration', help_page_path("integration", "bitbucket")}.
+ == #{link_to 'OAuth integration', help_page_path("integration/bitbucket")}.
diff --git a/app/views/projects/_builds_settings.html.haml b/app/views/projects/_builds_settings.html.haml
index 0568c2d305e..fff30f11d82 100644
--- a/app/views/projects/_builds_settings.html.haml
+++ b/app/views/projects/_builds_settings.html.haml
@@ -4,7 +4,7 @@
- unless @repository.gitlab_ci_yml
.form-group
%p Builds need to be configured before you can begin using Continuous Integration.
- = link_to 'Get started with Builds', help_page_path('ci/quick_start', 'README'), class: 'btn btn-info'
+ = link_to 'Get started with Builds', help_page_path('ci/quick_start/README'), class: 'btn btn-info'
.form-group
%p Get recent application code using the following command:
.radio
diff --git a/app/views/projects/_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/_gitlab_import_modal.html.haml b/app/views/projects/_gitlab_import_modal.html.haml
index 377cf0187b8..e9f39b16aa7 100644
--- a/app/views/projects/_gitlab_import_modal.html.haml
+++ b/app/views/projects/_gitlab_import_modal.html.haml
@@ -10,4 +10,4 @@
as administrator you need to configure
- else
ask your GitLab administrator to configure
- == #{link_to 'OAuth integration', help_page_path("integration", "gitlab")}.
+ == #{link_to 'OAuth integration', help_page_path("integration/gitlab")}.
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index 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"
- &middot;
- #{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"
+&middot;
+#{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 28a28282fd3..58d961d93ca 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -12,10 +12,19 @@
%li.confidential-issue-warning
= icon('warning')
%span This is a confidential issue. Your comment will not be visible to the public.
-
+
%li.pull-right
- %button.zen-control.zen-control-full.js-zen-enter{ type: 'button', tabindex: -1 }
- Go full screen
+ .toolbar-group
+ = markdown_toolbar_button({icon: "bold fw", data: { "md-tag" => "**" }, title: "Add bold text" })
+ = markdown_toolbar_button({icon: "italic fw", data: { "md-tag" => "*" }, title: "Add italic text" })
+ = markdown_toolbar_button({icon: "quote-right fw", data: { "md-tag" => "> ", "md-prepend" => true }, title: "Insert a quote" })
+ = markdown_toolbar_button({icon: "code fw", data: { "md-tag" => "`", "md-block" => "```" }, title: "Insert code" })
+ = markdown_toolbar_button({icon: "list-ul fw", data: { "md-tag" => "* ", "md-prepend" => true }, title: "Add a bullet list" })
+ = markdown_toolbar_button({icon: "list-ol fw", data: { "md-tag" => "1. ", "md-prepend" => true }, title: "Add a numbered list" })
+ = markdown_toolbar_button({icon: "check-square-o fw", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: "Add a task list" })
+ .toolbar-group
+ %button.toolbar-btn.js-zen-enter.has-tooltip.hidden-xs{ type: "button", tabindex: -1, aria: { label: "Go full screen" }, title: "Go full screen", data: { container: "body" } }
+ =icon("arrows-alt fw")
.md-write-holder
= yield
@@ -24,7 +33,7 @@
- if defined?(referenced_users) && referenced_users
%div.referenced-users.hide
%span
- = icon('exclamation-triangle')
+ = icon("exclamation-triangle")
You are about to add
%strong
%span.js-referenced-users-count 0
diff --git a/app/views/projects/_merge_request_settings.html.haml b/app/views/projects/_merge_request_settings.html.haml
index da522b53417..19b4249374b 100644
--- a/app/views/projects/_merge_request_settings.html.haml
+++ b/app/views/projects/_merge_request_settings.html.haml
@@ -8,4 +8,4 @@
%strong Only allow merge requests to be merged if the build succeeds
.help-block
Builds need to be configured to enable this feature.
- = link_to icon('question-circle'), help_page_path('workflow', 'merge_requests#only-allow-merge-requests-to-be-merged-if-the-build-succeeds')
+ = link_to icon('question-circle'), help_page_path('workflow/merge_requests', anchor: 'only-allow-merge-requests-to-be-merged-if-the-build-succeeds')
diff --git a/app/views/projects/badges/index.html.haml b/app/views/projects/badges/index.html.haml
index ee63bc55a30..ac80951dd4f 100644
--- a/app/views/projects/badges/index.html.haml
+++ b/app/views/projects/badges/index.html.haml
@@ -7,7 +7,7 @@
%b Builds badge &middot;
= @build_badge.to_html
.pull-right
- = render 'shared/ref_switcher', destination: 'badges'
+ = render 'shared/ref_switcher', destination: 'badges', align_right: true
.panel-body
.row
.col-md-2.text-center
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index ae89637df60..ff379bafb26 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -4,7 +4,9 @@
= icon('code-fork')
= ref
%span.editor-file-name
- = @path
+ - if current_action?(:edit) || current_action?(:update)
+ = text_field_tag 'file_path', (params[:file_path] || @path),
+ class: 'form-control new-file-path'
- if current_action?(:new) || current_action?(:create)
%span.editor-file-name
@@ -17,6 +19,8 @@
= dropdown_tag("Choose a License template", options: { toggle_class: 'js-license-selector', title: "Choose a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select, project: @project.name, fullname: @project.namespace.human_name } } )
.gitignore-selector.js-gitignore-selector-wrap.hidden
= dropdown_tag("Choose a .gitignore template", options: { toggle_class: 'js-gitignore-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } )
+ .gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.hidden
+ = dropdown_tag("Choose a GitLab CI Yaml template", options: { toggle_class: 'js-gitlab-ci-yml-selector', title: "Choose a template", filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls } } )
.encoding-selector
= select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2'
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/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml
index cab21f0cf19..dc57b49f27a 100644
--- a/app/views/projects/builds/_sidebar.html.haml
+++ b/app/views/projects/builds/_sidebar.html.haml
@@ -49,7 +49,7 @@
- if @build.duration
%p.build-detail-row
%span.build-light-text Duration:
- #{duration_in_words(@build.finished_at, @build.started_at)}
+ = time_interval_in_words(@build.duration)
- if @build.finished_at
%p.build-detail-row
%span.build-light-text Finished:
@@ -94,9 +94,9 @@
.block
.title
- Commit message
+ Commit title
%p.build-light-text.append-bottom-0
- #{@build.pipeline.git_commit_message}
+ #{@build.pipeline.git_commit_title}
- if @build.tags.any?
.block
diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml
index 181547316aa..2af625f69cd 100644
--- a/app/views/projects/builds/index.html.haml
+++ b/app/views/projects/builds/index.html.haml
@@ -2,7 +2,7 @@
- page_title "Builds"
= render "projects/pipelines/head"
-%div{ class: (container_class) }
+%div{ class: container_class }
.top-area
%ul.nav-links
%li{class: ('active' if @scope.nil?)}
@@ -11,17 +11,22 @@
%span.badge.js-totalbuilds-count
= number_with_delimiter(@all_builds.count(:id))
+ %li{class: ('active' if @scope == 'pending')}
+ = link_to project_builds_path(@project, scope: :pending) do
+ Pending
+ %span.badge
+ = number_with_delimiter(@all_builds.pending.count(:id))
%li{class: ('active' if @scope == 'running')}
= link_to project_builds_path(@project, scope: :running) do
Running
- %span.badge.js-running-count
- = number_with_delimiter(@all_builds.running_or_pending.count(:id))
+ %span.badge
+ = number_with_delimiter(@all_builds.running.count(:id))
%li{class: ('active' if @scope == 'finished')}
= link_to project_builds_path(@project, scope: :finished) do
Finished
- %span.badge.js-running-count
+ %span.badge
= number_with_delimiter(@all_builds.finished.count(:id))
.nav-controls
@@ -31,12 +36,12 @@
data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
- unless @repository.gitlab_ci_yml
- = link_to 'Get started with Builds', help_page_path('ci/quick_start', 'README'), class: 'btn btn-info'
+ = link_to 'Get started with Builds', help_page_path('ci/quick_start/README'), class: 'btn btn-info'
= link_to ci_lint_path, class: 'btn btn-default' do
%span CI Lint
- %ul.content-list
+ %ul.content-list.builds-content-list
- if @builds.blank?
%li
.nothing-here-block No builds to show
@@ -46,14 +51,10 @@
%thead
%tr
%th Status
- %th Build ID
%th Commit
- %th Ref
%th Stage
%th Name
- %th Tags
- %th Duration
- %th Finished at
+ %th
- if @project.build_coverage_enabled?
%th Coverage
%th
diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml
index 4e2702c2e44..4421f3b9562 100644
--- a/app/views/projects/builds/show.html.haml
+++ b/app/views/projects/builds/show.html.haml
@@ -67,4 +67,4 @@
= render "sidebar"
:javascript
- new CiBuild("#{namespace_project_build_url(@project.namespace, @project, @build)}", "#{@build.status}", "#{trace_with_state[:state]}")
+ new Build("#{namespace_project_build_url(@project.namespace, @project, @build)}", "#{namespace_project_build_url(@project.namespace, @project, @build, :json)}", "#{@build.status}", "#{trace_with_state[:state]}")
diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml
index 34ad9fe2c43..a098a082854 100644
--- a/app/views/projects/buttons/_fork.html.haml
+++ b/app/views/projects/buttons/_fork.html.haml
@@ -2,7 +2,7 @@
- if current_user && can?(current_user, :fork_project, @project)
- if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
= link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn has-tooltip' do
- = icon('code-fork fw')
+ = custom_icon('icon_fork')
Fork
%div.count-with-arrow
%span.arrow
@@ -10,10 +10,9 @@
= @project.forks_count
- else
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn has-tooltip' do
- = icon('code-fork fw')
+ = custom_icon('icon_fork')
Fork
%div.count-with-arrow
%span.arrow
- %span.count
- = link_to namespace_project_forks_path(@project.namespace, @project) do
- = @project.forks_count
+ = link_to namespace_project_forks_path(@project.namespace, @project), class: "count" do
+ = @project.forks_count
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index 5bd6e3f0ebc..9264289987d 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -1,32 +1,47 @@
-%tr.build
+%tr.build.commit
%td.status
- if can?(current_user, :read_build, build)
= ci_status_with_icon(build.status, namespace_project_build_url(build.project.namespace, build.project, build))
- else
= ci_status_with_icon(build.status)
- %td.build-link
- - if can?(current_user, :read_build, build)
- = link_to namespace_project_build_url(build.project.namespace, build.project, build) do
- %strong ##{build.id}
- - else
- %strong ##{build.id}
+ %td
+ .branch-commit
+ - if can?(current_user, :read_build, build)
+ = link_to namespace_project_build_url(build.project.namespace, build.project, build) do
+ %span ##{build.id}
+ - else
+ %span ##{build.id}
- - if build.stuck?
- = icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.')
- - if defined?(retried) && retried
- = icon('warning', class: 'text-warning has-tooltip', title: 'Build was retried.')
+ - if build.stuck?
+ = icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.')
+ - if defined?(retried) && retried
+ = icon('warning', class: 'text-warning has-tooltip', title: 'Build was retried.')
- - if defined?(commit_sha) && commit_sha
- %td
- = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace"
+ - if defined?(ref) && ref
+ - if build.ref
+ = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name"
+ - else
+ .light none
+ = custom_icon("icon_commit")
+
+ - if defined?(commit_sha) && commit_sha
+ = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "commit-id monospace"
+
+ .label-container
+ - if build.tags.any?
+ - build.tags.each do |tag|
+ %span.label.label-primary
+ = tag
+ - if build.try(:trigger_request)
+ %span.label.label-info triggered
+ - if build.try(:allow_failure)
+ %span.label.label-danger allowed to fail
+ - if defined?(retried) && retried
+ %span.label.label-warning retried
+ - if build.manual?
+ %span.label.label-info manual
- - if defined?(ref) && ref
- %td
- - if build.ref
- = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref)
- - else
- .light none
- if defined?(runner) && runner
%td
@@ -43,25 +58,14 @@
= build.name
%td
- .label-container
- - if build.tags.any?
- - build.tags.each do |tag|
- %span.label.label-primary
- = tag
- - if build.try(:trigger_request)
- %span.label.label-info triggered
- - if build.try(:allow_failure)
- %span.label.label-danger allowed to fail
- - if defined?(retried) && retried
- %span.label.label-warning retried
-
- %td.duration
- if build.duration
- #{duration_in_words(build.finished_at, build.started_at)}
-
- %td.timestamp
+ %p.duration
+ = custom_icon("icon_timer")
+ = duration_in_numbers(build.finished_at, build.started_at)
- if build.finished_at
- %span #{time_ago_with_tooltip(build.finished_at)}
+ %p.finished-at
+ = icon("calendar")
+ %span #{time_ago_with_tooltip(build.finished_at)}
- if defined?(coverage) && coverage
%td.coverage
@@ -77,6 +81,11 @@
- if build.active?
= link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do
= icon('remove', class: 'cred')
- - elsif defined?(allow_retry) && allow_retry && build.retryable?
- = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
- = icon('refresh')
+ - elsif defined?(allow_retry) && allow_retry
+ - if build.retryable?
+ = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do
+ = icon('repeat')
+ - elsif build.playable?
+ = link_to play_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do
+ = icon('play')
+
diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml
index b8d8758fd2b..cb0ca7bc8e3 100644
--- a/app/views/projects/ci/pipelines/_pipeline.html.haml
+++ b/app/views/projects/ci/pipelines/_pipeline.html.haml
@@ -1,20 +1,22 @@
- status = pipeline.status
%tr.commit
%td.commit-link
- = link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline.id), class: "ci-status ci-#{status}" do
- = ci_icon_for_status(status)
- %strong ##{pipeline.id}
+ = link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline.id) do
+ = ci_status_with_icon(status)
+
%td
- %div.branch-commit
+ .branch-commit
+ = link_to namespace_project_pipeline_path(@project.namespace, @project, pipeline.id) do
+ %span ##{pipeline.id}
- if pipeline.ref
- = link_to pipeline.ref, namespace_project_commits_path(@project.namespace, @project, pipeline.ref), class: "monospace"
- &middot;
+ .icon-container
+ = pipeline.tag? ? icon('tag') : icon('code-fork')
+ = link_to pipeline.ref, namespace_project_commits_path(@project.namespace, @project, pipeline.ref), class: "monospace branch-name"
+ .icon-container
+ = custom_icon("icon_commit")
= link_to pipeline.short_sha, namespace_project_commit_path(@project.namespace, @project, pipeline.sha), class: "commit-id monospace"
- &nbsp;
- - if pipeline.tag?
- %span.label.label-primary tag
- - elsif pipeline.latest?
+ - if pipeline.latest?
%span.label.label-success.has-tooltip{ title: 'Latest build for this branch' } latest
- if pipeline.triggered?
%span.label.label-primary triggered
@@ -24,15 +26,16 @@
%span.label.label-warning stuck
%p.commit-title
- - if commit_data = pipeline.commit_data
- = link_to_gfm truncate(commit_data.title, length: 60), namespace_project_commit_path(@project.namespace, @project, commit_data.id), class: "commit-row-message"
+ - if commit = pipeline.commit
+ = commit_author_avatar(commit, size: 20)
+ = link_to_gfm truncate(commit.title, length: 60), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "commit-row-message"
- else
Cant find HEAD commit for this branch
- - stages_status = pipeline.statuses.stages_status
+ - stages_status = pipeline.statuses.latest.stages_status
- stages.each do |stage|
- %td
+ %td.stage-cell
- status = stages_status[stage]
- tooltip = "#{stage.titleize}: #{status || 'not found'}"
- if status
@@ -45,27 +48,47 @@
%td
- if pipeline.started_at && pipeline.finished_at
%p.duration
- #{duration_in_words(pipeline.finished_at, pipeline.started_at)}
+ = custom_icon("icon_timer")
+ = duration_in_numbers(pipeline.finished_at, pipeline.started_at)
+ - if pipeline.finished_at
+ %p.finished-at
+ = icon("calendar")
+ #{time_ago_with_tooltip(pipeline.finished_at)}
- %td
+ %td.pipeline-actions
.controls.hidden-xs.pull-right
- artifacts = pipeline.builds.latest.select { |b| b.artifacts? }
- - if artifacts.present?
- .dropdown.inline.build-artifacts
- %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
- = icon('download')
- %b.caret
- %ul.dropdown-menu.dropdown-menu-align-right
- - artifacts.each do |build|
- %li
- = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, build), rel: 'nofollow' do
- = icon("download")
- %span Download '#{build.name}' artifacts
+ - actions = pipeline.manual_actions
+ - if artifacts.present? || actions.any?
+ .btn-group.inline
+ - if actions.any?
+ .btn-group
+ %a.dropdown-toggle.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'}
+ = icon("play")
+ %b.caret
+ %ul.dropdown-menu.dropdown-menu-align-right
+ - actions.each do |build|
+ %li
+ = link_to play_namespace_project_build_path(@project.namespace, @project, build), method: :post, rel: 'nofollow' do
+ = icon("play")
+ %span= build.name.humanize
+ - if artifacts.present?
+ .btn-group
+ %a.dropdown-toggle.btn.btn-default.build-artifacts{type: 'button', 'data-toggle' => 'dropdown'}
+ = icon("download")
+ %b.caret
+ %ul.dropdown-menu.dropdown-menu-align-right
+ - artifacts.each do |build|
+ %li
+ = link_to download_namespace_project_build_artifacts_path(@project.namespace, @project, build), rel: 'nofollow' do
+ = icon("download")
+ %span Download '#{build.name}' artifacts
- if can?(current_user, :update_pipeline, @project)
- - if pipeline.retryable?
- = link_to retry_namespace_project_pipeline_path(@project.namespace, @project, pipeline.id), class: 'btn has-tooltip', title: "Retry", method: :post do
- = icon("repeat")
- - if pipeline.cancelable?
- = link_to cancel_namespace_project_pipeline_path(@project.namespace, @project, pipeline.id), class: 'btn btn-remove has-tooltip', title: "Cancel", method: :post do
- = icon("remove")
+ .cancel-retry-btns.inline
+ - if pipeline.retryable?
+ = link_to retry_namespace_project_pipeline_path(@project.namespace, @project, pipeline.id), class: 'btn has-tooltip', title: "Retry", method: :post do
+ = icon("repeat")
+ - if pipeline.cancelable?
+ = link_to cancel_namespace_project_pipeline_path(@project.namespace, @project, pipeline.id), class: 'btn btn-remove has-tooltip', title: "Cancel", method: :post do
+ = icon("remove")
diff --git a/app/views/projects/commit/_ci_stage.html.haml b/app/views/projects/commit/_ci_stage.html.haml
index 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
&nbsp;
- = stage.titleize.pluralize
+ = stage.titleize
= render statuses.latest.ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, allow_retry: true
= render statuses.retried.ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, retried: true
%tr
diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml
index 0411137b7c6..41fd5459429 100644
--- a/app/views/projects/commit/_pipeline.html.haml
+++ b/app/views/projects/commit/_pipeline.html.haml
@@ -42,9 +42,7 @@
%th Status
%th Build ID
%th Name
- %th Tags
- %th Duration
- %th Finished at
+ %th
- if pipeline.project.build_coverage_enabled?
%th Coverage
%th
diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml
index 401cb4f7e30..d0da2606587 100644
--- a/app/views/projects/commit/show.html.haml
+++ b/app/views/projects/commit/show.html.haml
@@ -7,8 +7,7 @@
= render "ci_menu"
- else
%div.block-connector
-= render "projects/diffs/diffs", diffs: @diffs, project: @project,
- diff_refs: @diff_refs
+= render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @commit.diff_refs
= render "projects/notes/notes_with_form"
- if can_collaborate_with_project?
- %w(revert cherry-pick).each do |type|
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index a959b34a539..c8c7b858baa 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -10,29 +10,30 @@
= cache(cache_key) do
%li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" }
= commit_author_avatar(commit, size: 36)
- .commit-row-title
- %span.item-title
- = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
- %span.commit-row-message.visible-xs-inline
- &middot;
- = commit.short_id
- - if commit.status
- = render_commit_status(commit, cssclass: 'visible-xs-inline')
- - if commit.description?
- %a.text-expander.hidden-xs.js-toggle-button ...
+ .commit-info-block
+ .commit-row-title
+ %span.item-title
+ = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
+ %span.commit-row-message.visible-xs-inline
+ &middot;
+ = commit.short_id
+ - if commit.status
+ = render_commit_status(commit, cssclass: 'visible-xs-inline')
+ - if commit.description?
+ %a.text-expander.hidden-xs.js-toggle-button ...
- .commit-actions.hidden-xs
- - if commit.status
- = render_commit_status(commit, cssclass: 'btn btn-transparent')
- = clipboard_button_with_class({ clipboard_text: commit.id }, css_class: 'btn-transparent')
- = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-short-id btn btn-transparent"
- = link_to_browse_code(project, commit)
+ .commit-actions.hidden-xs
+ - if commit.status
+ = render_commit_status(commit, cssclass: 'btn btn-transparent')
+ = clipboard_button(clipboard_text: commit.id)
+ = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-short-id btn btn-transparent"
+ = link_to_browse_code(project, commit)
- - if commit.description?
- %pre.commit-row-description.js-toggle-content
- = preserve(markdown(escape_once(commit.description), pipeline: :single_line, author: commit.author))
+ - if commit.description?
+ %pre.commit-row-description.js-toggle-content
+ = preserve(markdown(escape_once(commit.description), pipeline: :single_line, author: commit.author))
- .commit-row-info
- = commit_author_link(commit, avatar: false, size: 24)
- authored
- #{time_ago_with_tooltip(commit.committed_date)}
+ .commit-row-info
+ = commit_author_link(commit, avatar: false, size: 24)
+ authored
+ #{time_ago_with_tooltip(commit.committed_date)}
diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml
index c8aa849c217..61152649907 100644
--- a/app/views/projects/commits/_head.html.haml
+++ b/app/views/projects/commits/_head.html.haml
@@ -1,7 +1,10 @@
-.scrolling-tabs-container
+.scrolling-tabs-container.sub-nav-scroll
+ .fade-left
+ = icon('angle-left')
+ .fade-right
+ = icon('angle-right')
.nav-links.sub-nav.scrolling-tabs
%ul{ class: (container_class) }
- .fade-left
= nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do
= link_to project_files_path(@project) do
Files
@@ -25,4 +28,3 @@
= nav_link(controller: [:tags, :releases]) do
= link_to namespace_project_tags_path(@project.namespace, @project) do
Tags
- .fade-right
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/_form.html.haml b/app/views/projects/compare/_form.html.haml
index dd590a4b8ec..af09b3418ea 100644
--- a/app/views/projects/compare/_form.html.haml
+++ b/app/views/projects/compare/_form.html.haml
@@ -2,15 +2,17 @@
.clearfix
- if params[:to] && params[:from]
= link_to 'switch', {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has-tooltip', title: 'Switch base of comparison'}
- .form-group
+ .form-group.dropdown.compare-form-group.js-compare-from-dropdown
.input-group.inline-input-group
%span.input-group-addon from
- = text_field_tag :from, params[:from], class: "form-control", required: true
+ = text_field_tag :from, params[:from], class: "form-control js-compare-dropdown", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from].presence }
+ = render "ref_dropdown"
= "..."
- .form-group
+ .form-group.dropdown.compare-form-group.js-compare-to-dropdown
.input-group.inline-input-group
%span.input-group-addon to
- = text_field_tag :to, params[:to], class: "form-control", required: true
+ = text_field_tag :to, params[:to], class: "form-control js-compare-dropdown", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to].presence }
+ = render "ref_dropdown"
&nbsp;
= button_tag "Compare", class: "btn btn-create commits-compare-btn"
- if @merge_request.present?
@@ -19,11 +21,3 @@
= link_to create_mr_path, class: 'prepend-left-10 btn' do
= icon("plus")
Create Merge Request
-
-:javascript
- var availableTags = #{@project.repository.ref_names.to_json};
-
- $("#from, #to").autocomplete({
- source: availableTags,
- minLength: 1
- });
diff --git a/app/views/projects/compare/_ref_dropdown.html.haml b/app/views/projects/compare/_ref_dropdown.html.haml
new file mode 100644
index 00000000000..c604c6d0135
--- /dev/null
+++ b/app/views/projects/compare/_ref_dropdown.html.haml
@@ -0,0 +1,4 @@
+.dropdown-menu.dropdown-menu-selectable
+ = dropdown_title "Select branch/tag"
+ = dropdown_content
+ = dropdown_loading
diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml
index c322942aeba..e9ff8e90dd5 100644
--- a/app/views/projects/compare/index.html.haml
+++ b/app/views/projects/compare/index.html.haml
@@ -2,8 +2,8 @@
- page_title "Compare"
= render "projects/commits/head"
-%div{ class: (container_class) }
- .row-content-block.second-block.content-component-block
+%div{ class: container_class }
+ .sub-header-block
Compare branches, tags or commit ranges.
%br
Fill input field with commit id like
diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml
index cdc34f51d6d..28a50e7031a 100644
--- a/app/views/projects/compare/show.html.haml
+++ b/app/views/projects/compare/show.html.haml
@@ -1,24 +1,24 @@
+- @no_container = true
- page_title "#{params[:from]}...#{params[:to]}"
= render "projects/commits/head"
+%div{ class: container_class }
+ .sub-header-block.no-bottom-space
+ = render "form"
-.row-content-block
- = render "form"
-
-- if @commits.present?
- .prepend-top-default
+ - if @commits.present?
= render "projects/commits/commit_list"
= render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @diff_refs
-- else
- .light-well.prepend-top-default
- .center
- %h4
- There isn't anything to compare.
- %p.slead
- - if params[:to] == params[:from]
- %span.label-branch #{params[:from]}
- and
- %span.label-branch #{params[:to]}
- are the same.
- - else
- You'll need to use different branch names to get a valid comparison.
+ - else
+ .light-well
+ .center
+ %h4
+ There isn't anything to compare.
+ %p.slead
+ - if params[:to] == params[:from]
+ %span.label-branch #{params[:from]}
+ and
+ %span.label-branch #{params[:to]}
+ are the same.
+ - else
+ You'll need to use different branch names to get a valid comparison.
diff --git a/app/views/projects/container_registry/_tag.html.haml b/app/views/projects/container_registry/_tag.html.haml
index f35faa6afb5..10822b6184c 100644
--- a/app/views/projects/container_registry/_tag.html.haml
+++ b/app/views/projects/container_registry/_tag.html.haml
@@ -3,9 +3,9 @@
= escape_once(tag.name)
= clipboard_button(clipboard_text: "docker pull #{tag.path}")
%td
- - if layer = tag.layers.first
- %span.has-tooltip{ title: "#{layer.revision}" }
- = layer.short_revision
+ - if tag.revision
+ %span.has-tooltip{ title: "#{tag.revision}" }
+ = tag.short_revision
- else
\-
%td
diff --git a/app/views/projects/deploy_keys/_form.html.haml b/app/views/projects/deploy_keys/_form.html.haml
index 894c36a96df..901605f7ca3 100644
--- a/app/views/projects/deploy_keys/_form.html.haml
+++ b/app/views/projects/deploy_keys/_form.html.haml
@@ -9,5 +9,5 @@
.form-group
%p.light.append-bottom-0
Paste a machine public key here. Read more about how to generate it
- = link_to "here", help_page_path("ssh", "README")
+ = link_to "here", help_page_path("ssh/README")
= f.submit "Add key", class: "btn-create btn"
diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml
new file mode 100644
index 00000000000..65d68aa2985
--- /dev/null
+++ b/app/views/projects/deployments/_actions.haml
@@ -0,0 +1,22 @@
+- if can?(current_user, :create_deployment, deployment) && deployment.deployable
+ .pull-right
+ - actions = deployment.manual_actions
+ - if actions.present?
+ .btn-group.inline
+ .btn-group
+ %a.dropdown-toggle.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'}
+ = icon("play")
+ %b.caret
+ %ul.dropdown-menu.dropdown-menu-align-right
+ - actions.each do |action|
+ %li
+ = link_to [:play, @project.namespace.becomes(Namespace), @project, action], method: :post, rel: 'nofollow' do
+ = icon("play")
+ %span= action.name.humanize
+
+ - if local_assigns.fetch(:allow_rollback, false)
+ = link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-build' do
+ - if deployment.last?
+ Retry
+ - else
+ Rollback
diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml
index d08dd92f1f6..baf02f1e6a0 100644
--- a/app/views/projects/deployments/_deployment.html.haml
+++ b/app/views/projects/deployments/_deployment.html.haml
@@ -7,17 +7,11 @@
%td
- if deployment.deployable
- = link_to namespace_project_build_path(@project.namespace, @project, deployment.deployable) do
+ = link_to [@project.namespace.becomes(Namespace), @project, deployment.deployable] do
= "#{deployment.deployable.name} (##{deployment.deployable.id})"
%td
#{time_ago_with_tooltip(deployment.created_at)}
%td
- - if can?(current_user, :create_deployment, deployment) && deployment.deployable
- .pull-right
- = link_to retry_namespace_project_build_path(@project.namespace, @project, deployment.deployable), method: :post, class: 'btn btn-build' do
- - if deployment.last?
- Retry
- - else
- Rollback
+ = render 'projects/deployments/actions', deployment: deployment, allow_rollback: true
diff --git a/app/views/projects/diffs/_content.html.haml b/app/views/projects/diffs/_content.html.haml
new file mode 100644
index 00000000000..a1b071f130c
--- /dev/null
+++ b/app/views/projects/diffs/_content.html.haml
@@ -0,0 +1,29 @@
+.diff-content.diff-wrap-lines
+ - # Skip all non non-supported blobs
+ - return unless blob.respond_to?(:text?)
+ - if diff_file.too_large?
+ .nothing-here-block This diff could not be displayed because it is too large.
+ - elsif blob.only_display_raw?
+ .nothing-here-block This file is too large to display.
+ - elsif blob_text_viewable?(blob)
+ - if !project.repository.diffable?(blob)
+ .nothing-here-block This diff was suppressed by a .gitattributes entry.
+ - elsif diff_file.collapsed?
+ - url = url_for(params.merge(action: :diff_for_path, old_path: diff_file.old_path, new_path: diff_file.new_path))
+ .nothing-here-block.diff-collapsed{data: { diff_for_path: url } }
+ This diff is collapsed. Click to expand it.
+ - elsif diff_file.diff_lines.length > 0
+ - if diff_view == 'parallel'
+ = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob
+ - else
+ = render "projects/diffs/text_file", diff_file: diff_file
+ - else
+ - if diff_file.mode_changed?
+ .nothing-here-block File mode changed
+ - elsif diff_file.renamed_file
+ .nothing-here-block File moved
+ - elsif blob.image?
+ - old_blob = diff_file.old_blob(diff_commit)
+ = render "projects/diffs/image", diff_file: diff_file, old_file: old_blob, file: blob
+ - else
+ .nothing-here-block No preview for this file type
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index f18bc8c41b3..8ae433b4823 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -2,10 +2,12 @@
- if diff_view == 'parallel'
- fluid_layout true
-- diff_files = safe_diff_files(diffs, diff_refs)
+- diff_files = safe_diff_files(diffs, diff_refs: diff_refs, repository: project.repository)
.content-block.oneline-block.files-changed
.inline-parallel-buttons
+ - if !expand_all_diffs? && diff_files.any? { |diff_file| diff_file.collapsed? }
+ = link_to 'Expand all', url_for(params.merge(expand_all_diffs: 1, format: 'html')), class: 'btn btn-default'
- if show_whitespace_toggle
- if current_controller?(:commit)
= commit_diff_whitespace_link(@project, @commit, class: 'hidden-xs')
@@ -21,10 +23,10 @@
- if diff_files.overflow?
= render 'projects/diffs/warning', diff_files: diff_files
-.files
+.files{data: {can_create_note: (!@diff_notes_disabled && can?(current_user, :create_note, @project))}}
- diff_files.each_with_index do |diff_file, index|
- diff_commit = commit_for_diff(diff_file)
- - blob = project.repository.blob_for_diff(diff_commit, diff_file)
+ - blob = diff_file.blob(diff_commit)
- next unless blob
- blob.load_all_data!(project.repository) unless blob.only_display_raw?
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index 2395ea3c275..c306909fb1a 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -1,29 +1,8 @@
-.diff-file.file-holder{id: "diff-#{i}", data: diff_file_html_data(project, diff_commit, diff_file)}
+.diff-file.file-holder{id: "diff-#{i}", data: diff_file_html_data(project, diff_file)}
.file-title{id: "file-path-#{hexdigest(diff_file.file_path)}"}
- - if diff_file.diff.submodule?
- %span
- = icon('archive fw')
- %span
- = submodule_link(blob, @commit.id, project.repository)
- - else
- = blob_icon blob.mode, blob.name
-
- = link_to "#diff-#{i}" do
- - if diff_file.renamed_file
- - old_path, new_path = mark_inline_diffs(diff_file.old_path, diff_file.new_path)
- = old_path
- &rarr;
- = new_path
- - else
- %span
- = diff_file.new_path
- - if diff_file.deleted_file
- deleted
-
- - if diff_file.mode_changed?
- %small
- = "#{diff_file.diff.a_mode} → #{diff_file.diff.b_mode}"
+ = render "projects/diffs/file_header", diff_file: diff_file, blob: blob, diff_commit: diff_commit, project: project, url: "#diff-#{i}"
+ - unless diff_file.submodule?
.file-actions.hidden-xs
- if blob_text_viewable?(blob)
= link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip btn-file-option', title: "Toggle comments for this file" do
@@ -37,22 +16,4 @@
= view_file_btn(diff_commit.id, diff_file, project)
- .diff-content.diff-wrap-lines
- - # Skip all non non-supported blobs
- - return unless blob.respond_to?(:text?)
- - if diff_file.too_large?
- .nothing-here-block This diff could not be displayed because it is too large.
- - elsif blob_text_viewable?(blob) && !project.repository.diffable?(blob)
- .nothing-here-block This diff was suppressed by a .gitattributes entry.
- - elsif blob_text_viewable?(blob)
- - if diff_view == 'parallel'
- = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i
- - else
- = render "projects/diffs/text_file", diff_file: diff_file, index: i
- - elsif blob.only_display_raw?
- .nothing-here-block This file is too large to display.
- - elsif blob.image?
- - old_file = project.repository.prev_blob_for_diff(diff_commit, diff_file)
- = render "projects/diffs/image", diff_file: diff_file, old_file: old_file, file: blob, index: i, diff_refs: diff_refs
- - else
- .nothing-here-block No preview for this file type
+ = render 'projects/diffs/content', diff_file: diff_file, diff_commit: diff_commit, diff_refs: diff_refs, blob: blob, project: project
diff --git a/app/views/projects/diffs/_file_header.html.haml b/app/views/projects/diffs/_file_header.html.haml
new file mode 100644
index 00000000000..95a2772fd0b
--- /dev/null
+++ b/app/views/projects/diffs/_file_header.html.haml
@@ -0,0 +1,25 @@
+- if defined?(blob) && blob && diff_file.submodule?
+ %span
+ = icon('archive fw')
+ %span
+ = submodule_link(blob, diff_commit.id, project.repository)
+- else
+ = conditional_link_to url.present?, url do
+ = blob_icon diff_file.b_mode, diff_file.file_path
+
+ - if diff_file.renamed_file
+ - old_path, new_path = mark_inline_diffs(diff_file.old_path, diff_file.new_path)
+ %strong
+ = old_path
+ &rarr;
+ %strong
+ = new_path
+ - else
+ %strong
+ = diff_file.new_path
+ - if diff_file.deleted_file
+ deleted
+
+ - if diff_file.mode_changed?
+ %small
+ = "#{diff_file.a_mode} → #{diff_file.b_mode}"
diff --git a/app/views/projects/diffs/_image.html.haml b/app/views/projects/diffs/_image.html.haml
index 2731219ccad..9ec6a7aa5cd 100644
--- a/app/views/projects/diffs/_image.html.haml
+++ b/app/views/projects/diffs/_image.html.haml
@@ -1,9 +1,8 @@
- diff = diff_file.diff
-- file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.id, diff.new_path))
+- file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(diff_file.new_ref, diff.new_path))
// diff_refs will be nil for orphaned commits (e.g. first commit in repo)
-- if diff_refs
- - old_commit_id = diff_refs.first.id
- - old_file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(old_commit_id, diff.old_path))
+- if diff_file.old_ref
+ - old_file_raw_path = namespace_project_raw_path(@project.namespace, @project, tree_join(diff_file.old_ref, diff.old_path))
- if diff.renamed_file || diff.new_file || diff.deleted_file
.image
@@ -16,7 +15,7 @@
%div.two-up.view
%span.wrap
.frame.deleted
- %a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(old_commit_id, diff.old_path))}
+ %a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.old_ref, diff.old_path))}
%img{src: old_file_raw_path}
%p.image-info.hide
%span.meta-filesize= "#{number_to_human_size old_file.size}"
@@ -28,7 +27,7 @@
%span.meta-height
%span.wrap
.frame.added
- %a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(@commit.id, diff.new_path))}
+ %a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.new_ref, diff.new_path))}
%img{src: file_raw_path}
%p.image-info.hide
%span.meta-filesize= "#{number_to_human_size file.size}"
diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml
index f1577e8a47b..5a8a131d10c 100644
--- a/app/views/projects/diffs/_line.html.haml
+++ b/app/views/projects/diffs/_line.html.haml
@@ -1,3 +1,6 @@
+- plain = local_assigns.fetch(:plain, false)
+- line_code = diff_file.line_code(line)
+- position = diff_file.position(line)
- type = line.type
%tr.line_holder{ id: line_code, class: type }
- case type
@@ -9,18 +12,16 @@
%td.new_line.diff-line-num
%td.line_content.match= line.text
- else
- %td.old_line.diff-line-num{ class: type, data: { linenumber: line.new_pos } }
- - link_text = type == "new" ? "&nbsp;".html_safe : line.old_pos
- - if defined?(plain) && plain
+ %td.old_line.diff-line-num{ class: type, data: { linenumber: line.old_pos } }
+ - link_text = type == "new" ? " " : line.old_pos
+ - if plain
= link_text
- else
- = link_to "", "##{line_code}", id: line_code, data: { linenumber: link_text }
- - if !@diff_notes_disabled && can?(current_user, :create_note, @project)
- = link_to_new_diff_note(line_code)
+ %a{href: "##{line_code}", data: { linenumber: link_text }}
%td.new_line.diff-line-num{ class: type, data: { linenumber: line.new_pos } }
- - link_text = type == "old" ? "&nbsp;".html_safe : line.new_pos
- - if defined?(plain) && plain
+ - link_text = type == "old" ? " " : line.new_pos
+ - if plain
= link_text
- else
- = link_to "", "##{line_code}", id: line_code, data: { linenumber: link_text }
- %td.line_content{ class: ['noteable_line', type, line_code], data: { line_code: line_code } }= diff_line_content(line.text, type)
+ %a{href: "##{line_code}", data: { linenumber: link_text }}
+ %td.line_content.noteable_line{ class: type, data: (diff_view_line_data(line_code, position, type) unless plain) }= diff_line_content(line.text, type)
diff --git a/app/views/projects/diffs/_match_line_parallel.html.haml b/app/views/projects/diffs/_match_line_parallel.html.haml
index 0cd888876e0..b9c0d9dcdfd 100644
--- a/app/views/projects/diffs/_match_line_parallel.html.haml
+++ b/app/views/projects/diffs/_match_line_parallel.html.haml
@@ -1,4 +1,4 @@
-%td.old_line.diff-line-num
+%td.old_line.diff-line-num.empty-cell
%td.line_content.parallel.match= line
-%td.new_line.diff-line-num
+%td.new_line.diff-line-num.empty-cell
%td.line_content.parallel.match= line
diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml
index 4ecc9528bd2..d208fcee10b 100644
--- a/app/views/projects/diffs/_parallel_view.html.haml
+++ b/app/views/projects/diffs/_parallel_view.html.haml
@@ -1,43 +1,36 @@
/ Side-by-side diff view
-%div.text-file.diff-wrap-lines.code.file-content.js-syntax-highlight
+%div.text-file.diff-wrap-lines.code.file-content.js-syntax-highlight{ data: diff_view_data }
%table
- diff_file.parallel_diff_lines.each do |line|
- left = line[:left]
- right = line[:right]
%tr.line_holder.parallel
- if left[:type] == 'match'
- = render "projects/diffs/match_line_parallel", { line: left[:text],
- line_old: left[:number], line_new: right[:number] }
+ = render "projects/diffs/match_line_parallel", { line: left[:text] }
- elsif left[:type] == 'nonewline'
- %td.old_line.diff-line-num
+ %td.old_line.diff-line-num.empty-cell
%td.line_content.parallel.match= left[:text]
- %td.new_line.diff-line-num
+ %td.new_line.diff-line-num.empty-cell
%td.line_content.parallel.match= left[:text]
- else
- %td.old_line.diff-line-num{id: left[:line_code], class: "#{left[:type]} #{'empty-cell' if !left[:number]}"}
- = link_to raw(left[:number]), "##{left[:line_code]}", id: left[:line_code]
- - if !@diff_notes_disabled && can?(current_user, :create_note, @project)
- = link_to_new_diff_note(left[:line_code], 'old')
- %td.line_content{class: "parallel noteable_line #{left[:type]} #{left[:line_code]} #{'empty-cell' if left[:text].empty?}", data: { line_code: left[:line_code] }}= diff_line_content(left[:text])
+ %td.old_line.diff-line-num{id: left[:line_code], class: [left[:type], ('empty-cell' unless left[:number])], data: { linenumber: left[:number] }}
+ %a{href: "##{left[:line_code]}" }= raw(left[:number])
+ %td.line_content.parallel.noteable_line{class: [left[:type], ('empty-cell' if left[:text].empty?)], data: diff_view_line_data(left[:line_code], left[:position], 'old')}= diff_line_content(left[:text])
- if right[:type] == 'new'
- - new_line_class = 'new'
+ - new_line_type = 'new'
- new_line_code = right[:line_code]
+ - new_position = right[:position]
- else
- - new_line_class = nil
+ - new_line_type = nil
- new_line_code = left[:line_code]
+ - new_position = left[:position]
- %td.new_line.diff-line-num{id: new_line_code, class: "#{new_line_class} #{'empty-cell' if !right[:number]}", data: { linenumber: right[:number] }}
- = link_to raw(right[:number]), "##{new_line_code}", id: new_line_code
- - if !@diff_notes_disabled && can?(current_user, :create_note, @project)
- = link_to_new_diff_note(new_line_code, 'new')
- %td.line_content.parallel{class: "noteable_line #{new_line_class} #{new_line_code} #{'empty-cell' if right[:text].empty?}", data: { line_code: new_line_code }}= diff_line_content(right[:text])
+ %td.new_line.diff-line-num{id: new_line_code, class: [new_line_type, ('empty-cell' unless right[:number])], data: { linenumber: right[:number] }}
+ %a{href: "##{new_line_code}" }= raw(right[:number])
+ %td.line_content.parallel.noteable_line{class: [new_line_type, ('empty-cell' if right[:text].empty?)], data: diff_view_line_data(new_line_code, new_position, 'new')}= diff_line_content(right[:text])
- unless @diff_notes_disabled
- notes_left, notes_right = organize_comments(left, right)
- if notes_left.present? || notes_right.present?
= render "projects/notes/diff_notes_with_reply_parallel", notes_left: notes_left, notes_right: notes_right
-
-- if diff_file.diff.diff.blank? && diff_file.mode_changed?
- .file-mode-changed
- File mode changed
diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml
index 068593a7dd1..196f8122db3 100644
--- a/app/views/projects/diffs/_text_file.html.haml
+++ b/app/views/projects/diffs/_text_file.html.haml
@@ -3,23 +3,18 @@
.suppressed-container
%a.show-suppressed-diff.js-show-suppressed-diff Changes suppressed. Click to show.
-%table.text-file.code.js-syntax-highlight{ class: too_big ? 'hide' : '' }
-
+%table.text-file.code.js-syntax-highlight{ data: diff_view_data, class: too_big ? 'hide' : '' }
- last_line = 0
- - diff_file.highlighted_diff_lines.each_with_index do |line, index|
- - line_code = generate_line_code(diff_file.file_path, line)
+ - diff_file.highlighted_diff_lines.each do |line|
- last_line = line.new_pos
- = render "projects/diffs/line", {line: line, diff_file: diff_file, line_code: line_code}
+ = render "projects/diffs/line", line: line, diff_file: diff_file
- unless @diff_notes_disabled
- - diff_notes = @grouped_diff_notes[line_code]
+ - line_code = diff_file.line_code(line)
+ - diff_notes = @grouped_diff_notes[line_code] if line_code
- if diff_notes
= render "projects/notes/diff_notes_with_reply", notes: diff_notes
- if last_line > 0
= render "projects/diffs/match_line", { line: "",
line_old: last_line, line_new: last_line, bottom: true, new_file: diff_file.new_file }
-
-- if diff_file.diff.blank? && diff_file.mode_changed?
- .file-mode-changed
- File mode changed
diff --git a/app/views/projects/diffs/_warning.html.haml b/app/views/projects/diffs/_warning.html.haml
index 15536c17f8e..10fa1ddf2e5 100644
--- a/app/views/projects/diffs/_warning.html.haml
+++ b/app/views/projects/diffs/_warning.html.haml
@@ -2,9 +2,6 @@
%h4
Too many changes to show.
.pull-right
- - unless diff_hard_limit_enabled?
- = link_to "Reload with full diff", url_for(params.merge(force_show_diff: true, format: nil)), class: "btn btn-sm"
-
- if current_controller?(:commit) or current_controller?(:merge_requests)
- if current_controller?(:commit)
= link_to "Plain diff", namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff), class: "btn btn-sm"
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 27a94fe02dc..57af167180b 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -23,7 +23,7 @@
.form-group.project-visibility-level-holder
= f.label :visibility_level, class: 'label-light' do
Visibility Level
- = link_to "(?)", help_page_path("public_access", "public_access")
+ = link_to "(?)", help_page_path("public_access/public_access")
- if can_change_visibility_level?(@project, current_user)
= render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: @project.visibility_level, form_model: @project)
- else
diff --git a/app/views/projects/environments/_environment.html.haml b/app/views/projects/environments/_environment.html.haml
index eafa246d05f..e2453395602 100644
--- a/app/views/projects/environments/_environment.html.haml
+++ b/app/views/projects/environments/_environment.html.haml
@@ -15,3 +15,6 @@
%td
- if last_deployment
#{time_ago_with_tooltip(last_deployment.created_at)}
+
+ %td
+ = render 'projects/deployments/actions', deployment: last_deployment
diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml
index ae9e77e7d89..a6dd34653ab 100644
--- a/app/views/projects/environments/index.html.haml
+++ b/app/views/projects/environments/index.html.haml
@@ -2,17 +2,25 @@
- page_title "Environments"
= render "projects/pipelines/head"
-%div{ class: (container_class) }
- - if can?(current_user, :create_environment, @project)
+%div{ class: container_class }
+ - if can?(current_user, :create_environment, @project) && !@environments.blank?
.top-area
.nav-controls
= link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do
New environment
- if @environments.blank?
- %ul.content-list.environments
- %li.nothing-here-block
- No environments to show
+ .blank-state.blank-state-no-icon
+ %h2.blank-state-title
+ You don't have any environments right now.
+ %p.blank-state-text
+ Environments are places where code gets deployed, such as staging or production.
+ %br
+ = succeed "." do
+ = link_to "Read more about environments", help_page_path("ci/environments")
+ - if can?(current_user, :create_environment, @project)
+ = link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do
+ New environment
- else
.table-holder
%table.table.environments
@@ -20,4 +28,5 @@
%th Environment
%th Last deployment
%th Date
+ %th
= render @environments
diff --git a/app/views/projects/environments/new.html.haml b/app/views/projects/environments/new.html.haml
index 54465828ba9..89e06567196 100644
--- a/app/views/projects/environments/new.html.haml
+++ b/app/views/projects/environments/new.html.haml
@@ -4,6 +4,9 @@
.col-lg-3
%h4.prepend-top-0
New Environment
- %p Environments allow you to track deployments of your application
+ %p
+ Environments allow you to track deployments of your application
+ = succeed "." do
+ = link_to "Read more about environments", help_page_path("ci/environments")
= render 'form'
diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml
index 069b77b5adf..b8b1ce52a91 100644
--- a/app/views/projects/environments/show.html.haml
+++ b/app/views/projects/environments/show.html.haml
@@ -2,10 +2,10 @@
- page_title "Environments"
= render "projects/pipelines/head"
-%div{ class: (container_class) }
+%div{ class: container_class }
.top-area
.col-md-9
- %h3.page-title= @environment.name.titleize
+ %h3.page-title= @environment.name.capitalize
.col-md-3
.nav-controls
@@ -13,10 +13,14 @@
= link_to 'Destroy', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to delete this environment?' }, class: 'btn btn-danger', method: :delete
- if @deployments.blank?
- %ul.content-list.environments
- %li.nothing-here-block
- No deployments for
- %strong= @environment.name
+ .blank-state.blank-state-no-icon
+ %h2.blank-state-title
+ You don't have any deployments right now.
+ %p.blank-state-text
+ Define environments in the deploy stage(s) in
+ %code .gitlab-ci.yml
+ to track deployments here.
+ = link_to "Read more", help_page_path("ci/environments"), class: "btn btn-success"
- else
.table-holder
%table.table.environments
diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml
index 4bcf2d9d533..a1d79bdabda 100644
--- a/app/views/projects/forks/index.html.haml
+++ b/app/views/projects/forks/index.html.haml
@@ -31,18 +31,12 @@
- if current_user && can?(current_user, :fork_project, @project)
- if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
= link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn btn-new' do
- = icon('code-fork fw')
+ = custom_icon('icon_fork')
Fork
- else
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn btn-new' do
- = icon('code-fork fw')
+ = custom_icon('icon_fork')
Fork
= render 'projects', projects: @forks
-
-- if @private_forks_count > 0
- .private-forks-notice
- = icon('lock fw', base: 'circle', class: 'fa-lg private-fork-icon')
- %strong= pluralize(@private_forks_count, 'private fork')
- %span you have no access to.
diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
index 5bc5c71283e..331dc1fcc29 100644
--- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
+++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
@@ -50,10 +50,12 @@
%td.duration
- if generic_commit_status.duration
- #{duration_in_words(generic_commit_status.finished_at, generic_commit_status.started_at)}
+ = icon("clock-o")
+ = time_interval_in_words(generic_commit_status.duration)
%td.timestamp
- if generic_commit_status.finished_at
+ = icon("calendar")
%span #{time_ago_with_tooltip(generic_commit_status.finished_at)}
- if defined?(coverage) && coverage
diff --git a/app/views/projects/graphs/_head.html.haml b/app/views/projects/graphs/_head.html.haml
index 8becaea246f..ca347406dfe 100644
--- a/app/views/projects/graphs/_head.html.haml
+++ b/app/views/projects/graphs/_head.html.haml
@@ -1,12 +1,16 @@
-- page_specific_javascripts asset_path("graphs/application.js")
-%ul.nav-links
- = nav_link(action: :show) do
- = link_to 'Contributors', namespace_project_graph_path
- = nav_link(action: :commits) do
- = link_to 'Commits', commits_namespace_project_graph_path
- = nav_link(action: :languages) do
- = link_to 'Languages', languages_namespace_project_graph_path
- - if @project.builds_enabled?
- = nav_link(action: :ci) do
- = link_to ci_namespace_project_graph_path do
- Continuous Integration
+.nav-links.sub-nav
+ %ul{ class: (container_class) }
+
+ - content_for :page_specific_javascripts do
+ = page_specific_javascript_tag('lib/chart.js')
+ = page_specific_javascript_tag('graphs/application.js')
+ = nav_link(action: :show) do
+ = link_to 'Contributors', namespace_project_graph_path
+ = nav_link(action: :commits) do
+ = link_to 'Commits', commits_namespace_project_graph_path
+ = nav_link(action: :languages) do
+ = link_to 'Languages', languages_namespace_project_graph_path
+ - if @project.builds_enabled?
+ = nav_link(action: :ci) do
+ = link_to ci_namespace_project_graph_path do
+ Continuous Integration
diff --git a/app/views/projects/graphs/ci.html.haml b/app/views/projects/graphs/ci.html.haml
index 19ccc125ea8..6be4273b6ab 100644
--- a/app/views/projects/graphs/ci.html.haml
+++ b/app/views/projects/graphs/ci.html.haml
@@ -1,15 +1,18 @@
+- @no_container = true
- page_title "Continuous Integration", "Graphs"
= render 'head'
-.row-content-block.append-bottom-default
- .oneline
- A collection of graphs for Continuous Integration
-#charts.ci-charts
- .row
- .col-md-6
- = render 'projects/graphs/ci/overall'
- .col-md-6
- = render 'projects/graphs/ci/build_times'
+%div{ class: container_class }
+ .sub-header-block
+ .oneline
+ A collection of graphs for Continuous Integration
- %hr
- = render 'projects/graphs/ci/builds'
+ #charts.ci-charts
+ .row
+ .col-md-6
+ = render 'projects/graphs/ci/overall'
+ .col-md-6
+ = render 'projects/graphs/ci/build_times'
+
+ %hr
+ = render 'projects/graphs/ci/builds'
diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml
index d9b2fb6c065..65db8af494d 100644
--- a/app/views/projects/graphs/commits.html.haml
+++ b/app/views/projects/graphs/commits.html.haml
@@ -1,52 +1,54 @@
+- @no_container = true
- page_title "Commits", "Graphs"
= render 'head'
-.row-content-block.append-bottom-default
- .tree-ref-holder
- = render 'shared/ref_switcher', destination: 'graphs_commits'
- %ul.breadcrumb.repo-breadcrumb
- = commits_breadcrumbs
+%div{ class: container_class }
+ .sub-header-block
+ .tree-ref-holder
+ = render 'shared/ref_switcher', destination: 'graphs_commits'
+ %ul.breadcrumb.repo-breadcrumb
+ = commits_breadcrumbs
-%p.lead
- Commit statistics for
- %strong #{@ref}
- #{@commits_graph.start_date.strftime('%b %d')} - #{@commits_graph.end_date.strftime('%b %d')}
+ %p.lead
+ Commit statistics for
+ %strong #{@ref}
+ #{@commits_graph.start_date.strftime('%b %d')} - #{@commits_graph.end_date.strftime('%b %d')}
-.row
- .col-md-6
- %ul
- %li
- %p.lead
- %strong #{@commits_graph.commits.size}
- commits during
- %strong #{@commits_graph.duration}
- days
- %li
- %p.lead
- Average
- %strong #{@commits_graph.commit_per_day}
- commits per day
- %li
- %p.lead
- Contributed by
- %strong #{@commits_graph.authors}
- authors
- .col-md-6
- %div
- %p.slead
- Commits per day of month
- %canvas#month-chart
-.row
- .col-md-6
- %div
- %p.slead
- Commits per day hour (UTC)
- %canvas#hour-chart
- .col-md-6
- %div
- %p.slead
- Commits per weekday
- %canvas#weekday-chart
+ .row
+ .col-md-6
+ %ul
+ %li
+ %p.lead
+ %strong #{@commits_graph.commits.size}
+ commits during
+ %strong #{@commits_graph.duration}
+ days
+ %li
+ %p.lead
+ Average
+ %strong #{@commits_graph.commit_per_day}
+ commits per day
+ %li
+ %p.lead
+ Contributed by
+ %strong #{@commits_graph.authors}
+ authors
+ .col-md-6
+ %div
+ %p.slead
+ Commits per day of month
+ %canvas#month-chart
+ .row
+ .col-md-6
+ %div
+ %p.slead
+ Commits per day hour (UTC)
+ %canvas#hour-chart
+ .col-md-6
+ %div
+ %p.slead
+ Commits per weekday
+ %canvas#weekday-chart
:javascript
var responsiveChart = function (selector, data) {
diff --git a/app/views/projects/graphs/languages.html.haml b/app/views/projects/graphs/languages.html.haml
index 249c16f4709..fcfcae0be20 100644
--- a/app/views/projects/graphs/languages.html.haml
+++ b/app/views/projects/graphs/languages.html.haml
@@ -1,24 +1,26 @@
+- @no_container = true
- page_title "Languages", "Graphs"
= render 'head'
-.row-content-block.append-bottom-default
- .oneline
- Programming languages used in this repository
+%div{ class: container_class }
+ .sub-header-block
+ .oneline
+ Programming languages used in this repository
-.row
- .col-md-8
- %canvas#languages-chart{ height: 400 }
- .col-md-4
- %ul.bordered-list
- - @languages.each do |language|
- %li
- %span{ style: "color: #{language[:color]}" }
- = icon('circle')
- &nbsp;
- = language[:label]
- .pull-right
- = language[:value]
- \%
+ .row
+ .col-md-8
+ %canvas#languages-chart{ height: 400 }
+ .col-md-4
+ %ul.bordered-list
+ - @languages.each do |language|
+ %li
+ %span{ style: "color: #{language[:color]}" }
+ = icon('circle')
+ &nbsp;
+ = language[:label]
+ .pull-right
+ = language[:value]
+ \%
:javascript
var data = #{@languages.to_json};
diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml
index 33970e7b909..a985b442b2d 100644
--- a/app/views/projects/graphs/show.html.haml
+++ b/app/views/projects/graphs/show.html.haml
@@ -1,29 +1,31 @@
+- @no_container = true
- page_title "Contributors", "Graphs"
= render 'head'
-.row-content-block.append-bottom-default
- .tree-ref-holder
- = render 'shared/ref_switcher', destination: 'graphs'
- %ul.breadcrumb.repo-breadcrumb
- = commits_breadcrumbs
-
-.loading-graph
- .center
- %h3.page-title
- %i.fa.fa-spinner.fa-spin
- Building repository graph.
- %p.slead Please wait a moment, this page will automatically refresh when ready.
-
-.stat-graph.hide
- .header.clearfix
- %h3#date_header.page-title
- %p.light
- Commits to #{@ref}, excluding merge commits. Limited to 6,000 commits.
- %input#brush_change{:type => "hidden"}
- .graphs
- #contributors-master
- #contributors.clearfix
- %ol.contributors-list.clearfix
+%div{ class: container_class }
+ .sub-header-block
+ .tree-ref-holder
+ = render 'shared/ref_switcher', destination: 'graphs'
+ %ul.breadcrumb.repo-breadcrumb
+ = commits_breadcrumbs
+
+ .loading-graph
+ .center
+ %h3.page-title
+ %i.fa.fa-spinner.fa-spin
+ Building repository graph.
+ %p.slead Please wait a moment, this page will automatically refresh when ready.
+
+ .stat-graph.hide
+ .header.clearfix
+ %h3#date_header.page-title
+ %p.light
+ Commits to #{@ref}, excluding merge commits. Limited to 6,000 commits.
+ %input#brush_change{:type => "hidden"}
+ .graphs.row
+ #contributors-master
+ #contributors.clearfix
+ %ol.contributors-list.clearfix
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..7612fe3719a 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -7,20 +7,36 @@
= auto_discovery_link_tag(:atom, namespace_project_issues_url(@project.namespace, @project, :atom, private_token: current_user.private_token), title: "#{@project.name} issues")
%div{ class: (container_class) }
- .top-area
- = render 'shared/issuable/nav', type: :issues
- .nav-controls
- - if current_user
- = link_to namespace_project_issues_path(@project.namespace, @project, :atom, { private_token: current_user.private_token }), class: 'btn append-right-10' do
- = icon('rss')
- %span.icon-label
- Subscribe
- = render 'shared/issuable/search_form', path: namespace_project_issues_path(@project.namespace, @project)
+ - if @project.issues.any?
+ .top-area
+ = render 'shared/issuable/nav', type: :issues
+ .nav-controls
+ - if current_user
+ = link_to namespace_project_issues_path(@project.namespace, @project, :atom, { private_token: current_user.private_token }), class: 'btn append-right-10' do
+ = icon('rss')
+ %span.icon-label
+ Subscribe
+ = render 'shared/issuable/search_form', path: namespace_project_issues_path(@project.namespace, @project)
+ - if can? current_user, :create_issue, @project
+ = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { assignee_id: @issuable_finder.assignee.try(:id), milestone_id: @issuable_finder.milestones.try(:first).try(:id) }), class: "btn btn-new", title: "New Issue", id: "new_issue_link" do
+ New Issue
+ = render 'shared/issuable/filter', type: :issues
+
+ .issues-holder
+ = render "issues"
+ - else
+ .blank-state.blank-state-welcome
+ %h2.blank-state-title.blank-state-welcome-title
+ Welcome to GitLab Issues
+ %p.blank-state-text
+ Code, test, and deploy together
+ .blank-state
+ .blank-state-icon
+ = custom_icon("issues", size: 50)
+ %h3.blank-state-title
+ You don't have any issues right now.
+ %p.blank-state-text
+ Issues are the best way to track your project progress
- if can? current_user, :create_issue, @project
- = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { assignee_id: @issuable_finder.assignee.try(:id), milestone_id: @issuable_finder.milestones.try(:first).try(:id) }), class: "btn btn-new", title: "New Issue", id: "new_issue_link" do
+ = link_to new_namespace_project_issue_path(@project.namespace, @project), class: "btn btn-new", title: "New Issue", id: "new_issue_link" do
New Issue
-
- = render 'shared/issuable/filter', type: :issues
-
- .issues-holder
- = render "issues"
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index 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/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml
index 393998f15b9..53dd300c35c 100644
--- a/app/views/projects/merge_requests/_discussion.html.haml
+++ b/app/views/projects/merge_requests/_discussion.html.haml
@@ -1,8 +1,8 @@
- content_for :note_actions do
- if can?(current_user, :update_merge_request, @merge_request)
- if @merge_request.open?
- = link_to 'Close merge request', merge_request_path(@merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-nr btn-comment btn-grouped btn-close close-mr-link js-note-target-close", title: "Close merge request", data: {original_text: "Close merge request", alternative_text: "Comment & close merge request"}
+ = link_to 'Close merge request', merge_request_path(@merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-nr btn-comment btn-close close-mr-link js-note-target-close", title: "Close merge request", data: {original_text: "Close merge request", alternative_text: "Comment & close merge request"}
- if @merge_request.closed?
- = link_to 'Reopen merge request', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-nr btn-comment btn-grouped btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request", data: {original_text: "Reopen merge request", alternative_text: "Comment & reopen merge request"}
+ = link_to 'Reopen merge request', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-nr btn-comment btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request", data: {original_text: "Reopen merge request", alternative_text: "Comment & reopen merge request"}
#notes= render "projects/notes/notes_with_form"
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/show/_how_to_merge.html.haml b/app/views/projects/merge_requests/show/_how_to_merge.html.haml
index b3bea900d42..b727efaa6a6 100644
--- a/app/views/projects/merge_requests/show/_how_to_merge.html.haml
+++ b/app/views/projects/merge_requests/show/_how_to_merge.html.haml
@@ -8,7 +8,7 @@
%p
%strong Step 1.
Fetch and check out the branch for this merge request
- = clipboard_button_with_class({clipboard_target: "pre#merge-info-1"}, css_class: "btn-clipboard")
+ = clipboard_button(clipboard_target: "pre#merge-info-1")
%pre.dark#merge-info-1
- if @merge_request.for_fork?
:preserve
@@ -25,7 +25,7 @@
%p
%strong Step 3.
Merge the branch and fix any conflicts that come up
- = clipboard_button_with_class({clipboard_target: "pre#merge-info-3"}, css_class: "btn-clipboard")
+ = clipboard_button(clipboard_target: "pre#merge-info-3")
%pre.dark#merge-info-3
- if @merge_request.for_fork?
:preserve
@@ -38,7 +38,7 @@
%p
%strong Step 4.
Push the result of the merge to GitLab
- = clipboard_button_with_class({clipboard_target: "pre#merge-info-4"}, css_class: "btn-clipboard")
+ = clipboard_button(clipboard_target: "pre#merge-info-4")
%pre.dark#merge-info-4
:preserve
git push origin #{h @merge_request.target_branch}
diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml
index 5bf5210aeab..b24bdf22ceb 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -19,13 +19,13 @@
Options
.dropdown-menu.dropdown-menu-align-right.hidden-lg
%ul
- %li{ class: issue_button_visibility(@merge_request, true) }
+ %li{ class: merge_request_button_visibility(@merge_request, true) }
= link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, title: 'Close merge request'
- %li{ class: issue_button_visibility(@merge_request, false) }
+ %li{ class: merge_request_button_visibility(@merge_request, false) }
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request'
%li
= link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'issuable-edit'
- = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "hidden-xs hidden-sm btn btn-grouped btn-close #{issue_button_visibility(@merge_request, true)}", title: 'Close merge request'
- = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "hidden-xs hidden-sm btn btn-grouped btn-reopen reopen-mr-link #{issue_button_visibility(@merge_request, false)}", title: 'Reopen merge request'
+ = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "hidden-xs hidden-sm btn btn-grouped btn-close #{merge_request_button_visibility(@merge_request, true)}", title: 'Close merge request'
+ = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "hidden-xs hidden-sm btn btn-grouped btn-reopen reopen-mr-link #{merge_request_button_visibility(@merge_request, false)}", title: 'Reopen merge request'
= link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "hidden-xs hidden-sm btn btn-grouped issuable-edit" do
Edit
diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml
index 08a38d283d2..489c632ae22 100644
--- a/app/views/projects/merge_requests/widget/_heading.html.haml
+++ b/app/views/projects/merge_requests/widget/_heading.html.haml
@@ -7,7 +7,7 @@
CI build
= ci_label_for_status(status)
for
- - commit = @merge_request.last_commit
+ - commit = @merge_request.diff_head_commit
= succeed "." do
= link_to @pipeline.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @pipeline.sha), class: "monospace"
%span.ci-coverage
@@ -24,7 +24,7 @@
CI build
= ci_label_for_status(status)
for
- - commit = @merge_request.last_commit
+ - commit = @merge_request.diff_head_commit
= succeed "." do
= link_to commit.short_id, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, commit), class: "monospace"
%span.ci-coverage
@@ -33,12 +33,12 @@
.ci_widget
= icon("spinner spin")
- Checking CI status for #{@merge_request.last_commit_short_sha}&hellip;
+ Checking CI status for #{@merge_request.diff_head_commit.short_id}&hellip;
.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/_conflicts.html.haml b/app/views/projects/merge_requests/widget/open/_conflicts.html.haml
index 06ab0a3fa00..f000cc38a65 100644
--- a/app/views/projects/merge_requests/widget/open/_conflicts.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_conflicts.html.haml
@@ -4,7 +4,7 @@
%p
Please resolve these conflicts or
- - if @merge_request.can_be_merged_by?(current_user)
+ - if @merge_request.can_be_merged_via_command_line_by?(current_user)
#{link_to "merge this request manually", "#modal_merge_info", class: "how_to_merge_link vlink", "data-toggle" => "modal"}.
- else
ask someone with write access to this repository to merge this request manually.
diff --git a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml
index 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 e4ab064eda8..091af4df4a1 100644
--- a/app/views/projects/network/show.html.haml
+++ b/app/views/projects/network/show.html.haml
@@ -1,8 +1,10 @@
- page_title "Network", @ref
-- page_specific_javascripts asset_path("network/application.js")
+- content_for :page_specific_javascripts do
+ = page_specific_javascript_tag('lib/raphael.js')
+ = page_specific_javascript_tag('network/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|
@@ -15,5 +17,5 @@
= check_box_tag :filter_ref, 1, @options[:filter_ref]
%span Begin with the selected commit
- .network-graph{ data: { url: '#{escape_javascript(@url)}', commit_url: '#{escape_javascript(@commit_url)}', ref: '#{escape_javascript(@ref)}', commit_id: '#{escape_javascript(@commit.id)}' } }
+ .network-graph{ data: { url: @url, commit_url: @commit_url, ref: @ref, commit_id: @commit.id } }
= spinner nil, true
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index 3c1c6060504..c72d0140bb9 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
+ .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)
- - if fogbugz_import_enabled?
- = link_to new_import_fogbugz_path, class: 'btn import_fogbugz' do
- %i.fa.fa-bug
- Fogbugz
-
- - if git_import_enabled?
- = link_to "#", class: 'btn js-toggle-button import_git' do
- %i.fa.fa-git
- %span Repo by URL
-
- - if gitlab_project_import_enabled?
- = link_to new_import_gitlab_project_path, class: 'btn import_gitlab_project project-submit' do
- %i.fa.fa-gitlab
- %span GitLab export
-
- .js-toggle-content.hide
- = render "shared/import_form", f: f
-
- .prepend-botton-10
-
- .form-group
- = f.label :description, class: 'control-label' do
- Description
- %span.light (optional)
- .col-sm-10
- = f.text_area :description, class: "form-control", rows: 3, maxlength: 250, tabindex: 3
- = render 'shared/visibility_level', f: f, visibility_level: default_project_visibility, can_change_visibility_level: true, form_model: @project
-
- .form-actions
= f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 4
= link_to 'Cancel', dashboard_projects_path, class: 'btn btn-cancel'
@@ -149,4 +154,9 @@
$('.import_gitlab_project').attr('disabled',true);
$('.import_gitlab_project').attr('title', 'Project path required.');
}
- })
+ });
+
+ $('.import_git').click(function( event ) {
+ $projectImportUrl = $('#project_import_url')
+ $projectImportUrl.attr('disabled', !$projectImportUrl.attr('disabled'))
+ }); \ No newline at end of file
diff --git a/app/views/projects/notes/_diff_notes_with_reply.html.haml b/app/views/projects/notes/_diff_notes_with_reply.html.haml
index 8144c1ba49e..ec6c4938efc 100644
--- a/app/views/projects/notes/_diff_notes_with_reply.html.haml
+++ b/app/views/projects/notes/_diff_notes_with_reply.html.haml
@@ -4,5 +4,4 @@
%td.notes_content
%ul.notes{ data: { discussion_id: note.discussion_id } }
= render partial: "projects/notes/note", collection: notes, as: :note
- .discussion-reply-holder
- = link_to_reply_discussion(note)
+ = link_to_reply_discussion(note)
diff --git a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
index 45986b0d1e8..e50a4f86d03 100644
--- a/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
+++ b/app/views/projects/notes/_diff_notes_with_reply_parallel.html.haml
@@ -8,8 +8,7 @@
%ul.notes{ data: { discussion_id: note_left.discussion_id } }
= render partial: "projects/notes/note", collection: notes_left, as: :note
- .discussion-reply-holder
- = link_to_reply_discussion(note_left, 'old')
+ = link_to_reply_discussion(note_left, 'old')
- else
%td.notes_line.old= ""
%td.notes_content.parallel.old= ""
@@ -20,8 +19,7 @@
%ul.notes{ data: { discussion_id: note_right.discussion_id } }
= render partial: "projects/notes/note", collection: notes_right, as: :note
- .discussion-reply-holder
- = link_to_reply_discussion(note_right, 'new')
+ = link_to_reply_discussion(note_right, 'new')
- else
%td.notes_line.new= ""
%td.notes_content.parallel.new= ""
diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml
index 03b3f6935d1..7c61ba750fe 100644
--- a/app/views/projects/notes/_form.html.haml
+++ b/app/views/projects/notes/_form.html.haml
@@ -7,6 +7,7 @@
= f.hidden_field :noteable_id
= f.hidden_field :noteable_type
= f.hidden_field :type
+ = f.hidden_field :position
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
= render 'projects/zen', f: f, attr: :note, classes: 'note-textarea js-note-text', placeholder: "Write a comment or drag your files here..."
diff --git a/app/views/projects/notes/_hints.html.haml b/app/views/projects/notes/_hints.html.haml
index 0b002043408..25466e7562e 100644
--- a/app/views/projects/notes/_hints.html.haml
+++ b/app/views/projects/notes/_hints.html.haml
@@ -1,8 +1,8 @@
.comment-toolbar.clearfix
.toolbar-text
Styling with
- = link_to 'Markdown', help_page_path('markdown', 'markdown'), target: '_blank', tabindex: -1
+ = link_to 'Markdown', help_page_path('markdown/markdown'), target: '_blank', tabindex: -1
is supported
%button.toolbar-button.markdown-selector{ type: 'button', tabindex: '-1' }
= icon('file-image-o', class: 'toolbar-button-icon')
- Attach a file
+ Attach a file \ No newline at end of file
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index bcdbff08011..af0046886fb 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -17,10 +17,10 @@
%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)
- - if access
+ - access = note_max_access_for_user(note)
+ - if access and not note.system
%span.note-role.hidden-xs= access
- - if current_user
+ - if current_user and not note.system
= link_to '#', title: 'Award Emoji', class: 'note-action-button note-emoji-button js-add-award js-note-emoji', data: { position: 'right' } do
= icon('spinner spin')
= icon('smile-o')
@@ -32,7 +32,7 @@
.note-body{class: note_editable ? 'js-task-list-container' : ''}
.note-text
= preserve do
- = markdown(note.note, pipeline: :note, cache_key: [note, "note"], author: note.author)
+ = note.note_html
= edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true)
- if note_editable
= render 'projects/notes/edit_form', note: note
diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml
index 1c39ce897a3..74538a9723e 100644
--- a/app/views/projects/notes/_notes_with_form.html.haml
+++ b/app/views/projects/notes/_notes_with_form.html.haml
@@ -2,6 +2,8 @@
= render "projects/notes/notes"
%ul.notes.notes-form.timeline
%li.timeline-entry
+ .flash-container.timeline-content
+
- if can? current_user, :create_note, @project
.timeline-icon.hidden-xs.hidden-sm
%a.author_link{ href: user_path(current_user) }
@@ -12,9 +14,9 @@
.disabled-comment.text-center
.disabled-comment-text.inline
Please
- = link_to "register",new_user_session_path
+ = link_to "register", new_session_path(:user, redirect_to_referer: 'yes')
or
- = link_to "login",new_user_session_path
+ = link_to "login", new_session_path(:user, redirect_to_referer: 'yes')
to post a comment
:javascript
diff --git a/app/views/projects/notes/discussions/_diff_with_notes.html.haml b/app/views/projects/notes/discussions/_diff_with_notes.html.haml
index 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" ? "&nbsp;".html_safe : line.old_pos } }
- %td.new_line.diff-line-num{ data: { linenumber: type == "old" ? "&nbsp;".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..5f466bdbac2 100644
--- a/app/views/projects/pipelines/index.html.haml
+++ b/app/views/projects/pipelines/index.html.haml
@@ -2,7 +2,7 @@
- page_title "Pipelines"
= render "projects/pipelines/head"
-%div{ class: (container_class) }
+%div{ class: container_class }
.top-area
%ul.nav-links
%li{class: ('active' if @scope.nil?)}
@@ -28,10 +28,10 @@
.nav-controls
- if can? current_user, :create_pipeline, @project
= link_to new_namespace_project_pipeline_path(@project.namespace, @project), class: 'btn btn-create' do
- New pipeline
+ Run pipeline
- unless @repository.gitlab_ci_yml
- = link_to 'Get started with Pipelines', help_page_path('ci/quick_start', 'README'), class: 'btn btn-info'
+ = link_to 'Get started with Pipelines', help_page_path('ci/quick_start/README'), class: 'btn btn-info'
= link_to ci_lint_path, class: 'btn btn-default' do
%span CI Lint
@@ -45,13 +45,13 @@
.table-holder
%table.table.builds
%tbody
- %th ID
+ %th Status
%th Commit
- stages.each do |stage|
%th.stage
%span.has-tooltip{ title: "#{stage.titleize}" }
- = stage.titleize.pluralize
- %th Duration
+ = stage.titleize
+ %th
%th
= render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages
diff --git a/app/views/projects/project_members/_group_members.html.haml b/app/views/projects/project_members/_group_members.html.haml
index cb6136c215a..e783d8c72c5 100644
--- a/app/views/projects/project_members/_group_members.html.haml
+++ b/app/views/projects/project_members/_group_members.html.haml
@@ -2,8 +2,7 @@
.panel-heading
%strong #{@group.name}
group members
- %small
- (#{members.count})
+ %span.badge= members.size
- if can?(current_user, :admin_group_member, @group)
.controls
= link_to 'Manage group members',
diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml
index 82892a33358..978c4dfc5ec 100644
--- a/app/views/projects/project_members/_new_project_member.html.haml
+++ b/app/views/projects/project_members/_new_project_member.html.haml
@@ -12,7 +12,7 @@
= select_tag :access_level, options_for_select(ProjectMember.access_level_roles, @project_member.access_level), class: "project-access-select select2"
.help-block
Read more about role permissions
- %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
+ %strong= link_to "here", help_page_path("user/permissions"), class: "vlink"
.form-actions
= f.submit 'Add users to project', class: "btn btn-create"
diff --git a/app/views/projects/project_members/_shared_group_members.html.haml b/app/views/projects/project_members/_shared_group_members.html.haml
index 952844acefc..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,7 @@
- @project_group_links.each do |group_links|
- shared_group = group_links.group
- - shared_group_users_count = group_links.group.group_members.count
+ - shared_group_members = shared_group.members
+ - shared_group_users_count = shared_group_members.size
.panel.panel-default
.panel-heading
Shared with
@@ -15,7 +16,7 @@
Edit group members
%ul.content-list
= render partial: 'shared/members/member',
- collection: shared_group.group_members.order(access_level: :desc).limit(20),
+ collection: shared_group_members.order(access_level: :desc).limit(20),
as: :member,
locals: { show_controls: false, show_roles: false }
- if shared_group_users_count > 20
diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml
index 03207614258..b0bfdd235f7 100644
--- a/app/views/projects/project_members/_team.html.haml
+++ b/app/views/projects/project_members/_team.html.haml
@@ -2,8 +2,7 @@
.panel-heading
%strong #{@project.name}
project members
- %small
- (#{members.count})
+ %span.badge= members.size
.controls
= form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do
.form-group
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index 357ccccaf1d..9031f01b496 100644
--- a/app/views/projects/project_members/index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -13,9 +13,9 @@
Users with access to this project are listed below.
= render "new_project_member"
- = render 'shared/members/requests', membership_source: @project, members: @project_members.request
+ = render 'shared/members/requests', membership_source: @project, requesters: @requesters
- = render 'team', members: @project_members.non_request
+ = render 'team', members: @project_members
- if @group
= render "group_members", members: @group_members
diff --git a/app/views/projects/protected_branches/_branches_list.html.haml b/app/views/projects/protected_branches/_branches_list.html.haml
index 565905cbe7b..720d67dff7c 100644
--- a/app/views/projects/protected_branches/_branches_list.html.haml
+++ b/app/views/projects/protected_branches/_branches_list.html.haml
@@ -1,6 +1,6 @@
%h5.prepend-top-0
- Already Protected (#{@branches.size})
-- if @branches.empty?
+ Already Protected (#{@protected_branches.size})
+- if @protected_branches.empty?
%p.settings-message.text-center
No branches are protected, protect a branch with the form above.
- else
@@ -8,34 +8,21 @@
.table-responsive
%table.table.protected-branches-list
%colgroup
+ %col{ width: "20%" }
%col{ width: "30%" }
- %col{ width: "30%" }
+ %col{ width: "25%" }
%col{ width: "25%" }
- if can_admin_project
%col
%thead
%tr
- %th Branch
- %th Last commit
- %th Developers can push
+ %th Protected Branch
+ %th Commit
+ %th Developers Can Push
+ %th Developers Can Merge
- if can_admin_project
%th
%tbody
- - @branches.each do |branch|
- - @url = namespace_project_protected_branch_path(@project.namespace, @project, branch)
- %tr
- %td
- = link_to(branch.name, namespace_project_commits_path(@project.namespace, @project, branch.name))
- - if @project.root_ref?(branch.name)
- %span.label.label-info.prepend-left-5 default
- %td
- - if commit = branch.commit
- = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit_short_id')
- #{time_ago_with_tooltip(commit.committed_date)}
- - else
- (branch was removed from repository)
- %td
- = check_box_tag("developers_can_push", branch.id, branch.developers_can_push, data: { url: @url })
- - if can_admin_project
- %td
- = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-warning btn-sm"
+ = render partial: @protected_branches, locals: { can_admin_project: can_admin_project }
+
+ = paginate @protected_branches, theme: 'gitlab'
diff --git a/app/views/projects/protected_branches/_dropdown.html.haml b/app/views/projects/protected_branches/_dropdown.html.haml
new file mode 100644
index 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..7fda7f96047
--- /dev/null
+++ b/app/views/projects/protected_branches/_protected_branch.html.haml
@@ -0,0 +1,23 @@
+- url = namespace_project_protected_branch_path(@project.namespace, @project, protected_branch)
+%tr
+ %td
+ = protected_branch.name
+ - if @project.root_ref?(protected_branch.name)
+ %span.label.label-info.prepend-left-5 default
+ %td
+ - if protected_branch.wildcard?
+ - matching_branches = protected_branch.matching(repository.branches)
+ = link_to pluralize(matching_branches.count, "matching branch"), namespace_project_protected_branch_path(@project.namespace, @project, protected_branch)
+ - else
+ - if commit = protected_branch.commit
+ = link_to(commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit_short_id')
+ = time_ago_with_tooltip(commit.committed_date)
+ - else
+ (branch was removed from repository)
+ %td
+ = check_box_tag("developers_can_push", protected_branch.id, protected_branch.developers_can_push, data: { url: url })
+ %td
+ = check_box_tag("developers_can_merge", protected_branch.id, protected_branch.developers_can_merge, data: { url: url })
+ - if can_admin_project
+ %td
+ = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-warning btn-sm pull-right"
diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml
index c7d317dbaee..151e1d64851 100644
--- a/app/views/projects/protected_branches/index.html.haml
+++ b/app/views/projects/protected_branches/index.html.haml
@@ -4,30 +4,46 @@
.col-lg-3
%h4.prepend-top-0
= page_title
- %p Keep stable branches secure and force developers to use Merge Requests
- .col-lg-9
- %h5.prepend-top-0
- Protect a branch
- .account-well.append-bottom-default
- %p.light-header.append-bottom-0 Protected branches are designed to
+ %p Keep stable branches secure and force developers to use merge requests.
+ %p.prepend-top-20
+ Protected branches are designed to:
%ul
- %li prevent pushes from everybody except #{link_to "masters", help_page_path("permissions", "permissions"), class: "vlink"}
+ %li prevent pushes from everybody except #{link_to "masters", help_page_path("user/permissions"), class: "vlink"}
%li prevent anyone from force pushing to the branch
%li prevent anyone from deleting the branch
- %p.append-bottom-0 Read more about #{link_to "project permissions", help_page_path("permissions", "permissions"), class: "underlined-link"}
+ %p.append-bottom-0 Read more about #{link_to "project permissions", help_page_path("user/permissions"), class: "underlined-link"}
+ .col-lg-9
+ %h5.prepend-top-0
+ Protect a branch
- if can? current_user, :admin_project, @project
= form_for [@project.namespace.becomes(Namespace), @project, @protected_branch] do |f|
= form_errors(@protected_branch)
.form-group
= f.label :name, "Branch", class: "label-light"
- = f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: true}, {class: "select2", data: {placeholder: "Select branch"}})
+ = render partial: "dropdown", locals: { f: f }
+ %p.help-block
+ = link_to "Wildcards", help_page_path('workflow/protected_branches', anchor: "wildcard-protected-branches")
+ such as
+ %code *-stable
+ or
+ %code production/*
+ are supported.
+
.form-group
= f.check_box :developers_can_push, class: "pull-left"
.prepend-left-20
= f.label :developers_can_push, "Developers can push", class: "label-light append-bottom-0"
%p.light.append-bottom-0
Allow developers to push to this branch
- = f.submit "Protect", class: "btn-create btn"
+
+ .form-group
+ = f.check_box :developers_can_merge, class: "pull-left"
+ .prepend-left-20
+ = f.label :developers_can_merge, "Developers can merge", class: "label-light append-bottom-0"
+ %p.light.append-bottom-0
+ Allow developers to accept merge requests to this branch
+ = f.submit "Protect", class: "btn-create btn protect-branch-btn", disabled: true
+
%hr
= render "branches_list"
diff --git a/app/views/projects/protected_branches/show.html.haml b/app/views/projects/protected_branches/show.html.haml
new file mode 100644
index 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/runners/_form.html.haml b/app/views/projects/runners/_form.html.haml
index d62f5c8f131..c45a9d4f81f 100644
--- a/app/views/projects/runners/_form.html.haml
+++ b/app/views/projects/runners/_form.html.haml
@@ -13,6 +13,12 @@
= f.check_box :run_untagged
%span.light Indicates whether this runner can pick jobs without tags
.form-group
+ = label :locked, 'Lock to current projects', class: 'control-label'
+ .col-sm-10
+ .checkbox
+ = f.check_box :locked
+ %span.light When a runner is locked, it cannot be assigned to other projects
+ .form-group
= label_tag :token, class: 'control-label' do
Token
.col-sm-10
diff --git a/app/views/projects/runners/_runner.html.haml b/app/views/projects/runners/_runner.html.haml
index 96e2aac451f..85225857758 100644
--- a/app/views/projects/runners/_runner.html.haml
+++ b/app/views/projects/runners/_runner.html.haml
@@ -2,8 +2,10 @@
%h4
= runner_status_icon(runner)
%span.monospace
- - if @runners.include?(runner)
+ - if @project_runners.include?(runner)
= link_to runner.short_sha, runner_path(runner)
+ - if runner.locked?
+ = icon('lock', class: 'has-tooltip', title: 'Locked to current projects')
%small
= link_to edit_namespace_project_runner_path(@project.namespace, @project, runner) do
%i.fa.fa-edit.btn
@@ -11,7 +13,7 @@
= runner.short_sha
.pull-right
- - if @runners.include?(runner)
+ - if @project_runners.include?(runner)
- if runner.belongs_to_one_project?
= link_to 'Remove runner', runner_path(runner), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn btn-danger btn-sm'
- else
diff --git a/app/views/projects/runners/_specific_runners.html.haml b/app/views/projects/runners/_specific_runners.html.haml
index 8ae9f0d95f7..d469dda5b81 100644
--- a/app/views/projects/runners/_specific_runners.html.haml
+++ b/app/views/projects/runners/_specific_runners.html.haml
@@ -17,13 +17,13 @@
Start runner!
-- if @runners.any?
+- if @project_runners.any?
%h4.underlined-title Runners activated for this project
%ul.bordered-list.activated-specific-runners
- = render partial: 'runner', collection: @runners, as: :runner
+ = render partial: 'runner', collection: @project_runners, as: :runner
-- if @specific_runners.any?
+- if @assignable_runners.any?
%h4.underlined-title Available specific runners
%ul.bordered-list.available-specific-runners
- = render partial: 'runner', collection: @specific_runners, as: :runner
- = paginate @specific_runners
+ = render partial: 'runner', collection: @assignable_runners, as: :runner
+ = paginate @assignable_runners
diff --git a/app/views/projects/runners/show.html.haml b/app/views/projects/runners/show.html.haml
index f24e1b9144e..61b99f35d74 100644
--- a/app/views/projects/runners/show.html.haml
+++ b/app/views/projects/runners/show.html.haml
@@ -23,6 +23,9 @@
%td Can run untagged jobs
%td= @runner.run_untagged? ? 'Yes' : 'No'
%tr
+ %td Locked to this project
+ %td= @runner.locked? ? 'Yes' : 'No'
+ %tr
%td Tags
%td
- @runner.tag_list.each do |tag|
diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml
index 1f13ea28b4e..166dc4a01fc 100644
--- a/app/views/projects/services/_form.html.haml
+++ b/app/views/projects/services/_form.html.haml
@@ -12,5 +12,5 @@
&nbsp;
- if @service.valid? && @service.activated?
- disabled = @service.can_test? ? '':'disabled'
- = link_to 'Test settings', test_namespace_project_service_path(@project.namespace, @project, @service.to_param), class: "btn #{disabled}"
+ = link_to 'Test settings', test_namespace_project_service_path(@project.namespace, @project, @service), class: "btn #{disabled}", title: @service.disabled_title
= link_to "Cancel", namespace_project_services_path(@project.namespace, @project), class: "btn btn-cancel"
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index e9ca46a74bf..dd1cf680cfa 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -12,56 +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
+ .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?
@@ -71,4 +82,4 @@
Archived project! Repository is read-only
%div{class: "project-show-#{default_project_view}"}
- = render default_project_view
+ = render default_project_view \ No newline at end of file
diff --git a/app/views/projects/snippets/_actions.html.haml b/app/views/projects/snippets/_actions.html.haml
index bf57beb9d07..bdbf3e5f4d6 100644
--- a/app/views/projects/snippets/_actions.html.haml
+++ b/app/views/projects/snippets/_actions.html.haml
@@ -1,27 +1,29 @@
.hidden-xs
- = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-create new-snippet-link', title: "New Snippet" do
- = icon('plus')
- New Snippet
+ - if can?(current_user, :create_project_snippet, @project)
+ = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-create new-snippet-link', title: "New Snippet" do
+ New Snippet
- if can?(current_user, :update_project_snippet, @snippet)
= link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped snippable-edit" do
Edit
- if can?(current_user, :update_project_snippet, @snippet)
= link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-warning", title: 'Delete Snippet' do
Delete
-.visible-xs-block.dropdown
- %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
- Options
- %span.caret
- .dropdown-menu.dropdown-menu-full-width
- %ul
- %li
- = link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New Snippet" do
- New Snippet
- - if can?(current_user, :update_project_snippet, @snippet)
- %li
- = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet) do
- Edit
- - if can?(current_user, :update_project_snippet, @snippet)
- %li
- = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
- Delete
+- if can?(current_user, :create_project_snippet, @project) || can?(current_user, :update_project_snippet, @snippet)
+ .visible-xs-block.dropdown
+ %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
+ Options
+ %span.caret
+ .dropdown-menu.dropdown-menu-full-width
+ %ul
+ - if can?(current_user, :create_project_snippet, @project)
+ %li
+ = link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New Snippet" do
+ New Snippet
+ - if can?(current_user, :update_project_snippet, @snippet)
+ %li
+ = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet) do
+ Edit
+ - if can?(current_user, :update_project_snippet, @snippet)
+ %li
+ = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
+ Delete
diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml
index 96fee3b17b2..1646bcf4b8a 100644
--- a/app/views/projects/snippets/index.html.haml
+++ b/app/views/projects/snippets/index.html.haml
@@ -1,10 +1,10 @@
- page_title "Snippets"
-.row-content-block.top-block
+.sub-header-block
.pull-right
- = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New Snippet" do
- = icon('plus')
- New Snippet
+ - if can?(current_user, :create_project_snippet, @project)
+ = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New Snippet" do
+ New Snippet
.oneline
Share code pastes with others out of git repository
diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml
index 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/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index b7d7d5c5382..395d7af6cbb 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -1,36 +1,39 @@
+- @no_container = true
- page_title @tag.name, "Tags"
= render "projects/commits/head"
-.row-content-block
- .pull-right
- - if can?(current_user, :push_code, @project)
- = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn-grouped btn has-tooltip', title: 'Edit release notes' do
- = icon("pencil")
- = link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped has-tooltip', title: 'Browse files' do
- = icon('files-o')
- = link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn btn-grouped has-tooltip', title: 'Browse commits' do
- = icon('history')
- - if can? current_user, :download_code, @project
- = render 'projects/tags/download', ref: @tag.name, project: @project
- - if can?(current_user, :admin_project, @project)
- .pull-right
- = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped has-tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
- %i.fa.fa-trash-o
- .title
- %span.item-title= @tag.name
- - if @commit
- = render 'projects/branches/commit', commit: @commit, project: @project
- - else
- Cant find HEAD commit for this tag
- - if @tag.message.present?
- %pre.body
- = strip_gpg_signature(@tag.message)
+%div{ class: container_class }
+ .sub-header-block
+ .pull-right.tag-buttons
+ - if can?(current_user, :push_code, @project)
+ = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Edit release notes' do
+ = icon("pencil")
+ = link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Browse files' do
+ = icon('files-o')
+ = link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Browse commits' do
+ = icon('history')
+ - if can? current_user, :download_code, @project
+ = render 'projects/tags/download', ref: @tag.name, project: @project
+ - if can?(current_user, :admin_project, @project)
+ .pull-right
+ = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped has-tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do
+ %i.fa.fa-trash-o
+ .tag-info.append-bottom-10
+ .title
+ %span.item-title= @tag.name
+ - if @commit
+ = render 'projects/branches/commit', commit: @commit, project: @project
+ - else
+ Cant find HEAD commit for this tag
+ - if @tag.message.present?
+ %pre.body
+ = strip_gpg_signature(@tag.message)
-.append-bottom-default.prepend-top-default
- - if @release.description.present?
- .description
- .wiki
- = preserve do
- = markdown @release.description
- - else
- This tag has no release notes.
+ .append-bottom-default.prepend-top-default
+ - if @release.description.present?
+ .description
+ .wiki
+ = preserve do
+ = markdown @release.description
+ - else
+ This tag has no release notes.
diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml
index 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/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml
index 4faa547769b..4ea75dbbf0c 100644
--- a/app/views/projects/wikis/_main_links.html.haml
+++ b/app/views/projects/wikis/_main_links.html.haml
@@ -1,4 +1,7 @@
- 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
= link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn" do
Page History
- if can?(current_user, :create_wiki, @project)
diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml
index 988fe024e28..f8ea479e0b1 100644
--- a/app/views/projects/wikis/_nav.html.haml
+++ b/app/views/projects/wikis/_nav.html.haml
@@ -1,5 +1,5 @@
-.top-area
- %ul.nav-links
+.nav-links.sub-nav
+ %ul{ class: (container_class) }
= nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do
= link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home)
@@ -10,9 +10,4 @@
= link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do
Git Access
- .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
-
-= render 'projects/wikis/new'
+ = render 'projects/wikis/new'
diff --git a/app/views/projects/wikis/_new.html.haml b/app/views/projects/wikis/_new.html.haml
index 919daf0a7b2..c32cb122c26 100644
--- a/app/views/projects/wikis/_new.html.haml
+++ b/app/views/projects/wikis/_new.html.haml
@@ -1,14 +1,17 @@
-%div#modal-new-wiki.modal
- .modal-dialog
- .modal-content
- .modal-header
- %a.close{href: "#", "data-dismiss" => "modal"} ×
- %h3.page-title New Wiki Page
- .modal-body
- %form.new-wiki-page
- .form-group
- = label_tag :new_wiki_path do
- %span Page slug
- = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => namespace_project_wikis_path(@project.namespace, @project), autofocus: true
- .form-actions
- = button_tag 'Create Page', class: 'build-new-wiki btn btn-create'
+- @no_container = true
+
+%div{ class: container_class }
+ %div#modal-new-wiki.modal
+ .modal-dialog
+ .modal-content
+ .modal-header
+ %a.close{href: "#", "data-dismiss" => "modal"} ×
+ %h3.page-title New Wiki Page
+ .modal-body
+ %form.new-wiki-page
+ .form-group
+ = label_tag :new_wiki_path do
+ %span Page slug
+ = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => namespace_project_wikis_path(@project.namespace, @project), autofocus: true
+ .form-actions
+ = button_tag 'Create Page', class: 'build-new-wiki btn btn-create'
diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml
index cbd69ee1a73..233538bb488 100644
--- a/app/views/projects/wikis/edit.html.haml
+++ b/app/views/projects/wikis/edit.html.haml
@@ -1,19 +1,25 @@
+- @no_container = true
- page_title "Edit", @page.title.capitalize, "Wiki"
= render 'nav'
-.top-area
- .nav-text.wiki-page
- %strong
- - if @page.persisted?
- = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page)
- - else
- = @page.title.capitalize
- %span.light
- &middot;
- Edit Page
+%div{ class: container_class }
+ .top-area
+ .nav-text
+ %strong
+ - if @page.persisted?
+ = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page)
+ - else
+ = @page.title.capitalize
+ %span.light
+ &middot;
+ Edit Page
- .nav-controls
- = render 'main_links'
+ .nav-controls
+ - 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'
-= render 'form'
+ = render 'form'
diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml
index ccceab6155e..b8811a28dd6 100644
--- a/app/views/projects/wikis/git_access.html.haml
+++ b/app/views/projects/wikis/git_access.html.haml
@@ -1,32 +1,34 @@
+- @no_container = true
- page_title "Git Access", "Wiki"
= render 'nav'
-.row-content-block
- %span.oneline
- Git access for
- %strong= @project_wiki.path_with_namespace
+%div{ class: container_class }
+ .sub-header-block
+ %span.oneline
+ Git access for
+ %strong= @project_wiki.path_with_namespace
- .pull-right
- = render "shared/clone_panel", project: @project_wiki
+ .pull-right
+ = render "shared/clone_panel", project: @project_wiki
-.git-empty.prepend-top-default
- %fieldset
- %legend Install Gollum:
- %pre.dark
- :preserve
- gem install gollum
+ .prepend-top-default
+ %fieldset
+ %legend Install Gollum:
+ %pre.dark
+ :preserve
+ gem install gollum
- %legend Clone Your Wiki:
- %pre.dark
- :preserve
- git clone #{ content_tag(:span, default_url_to_repo(@project_wiki), class: 'clone')}
- cd #{h @project_wiki.path}
+ %legend Clone Your Wiki:
+ %pre.dark
+ :preserve
+ git clone #{ content_tag(:span, default_url_to_repo(@project_wiki), class: 'clone')}
+ cd #{h @project_wiki.path}
- %legend Start Gollum And Edit Locally:
- %pre.dark
- :preserve
- gollum
- == Sinatra/1.3.5 has taken the stage on 4567 for development with backup from Thin
- >> Thin web server (v1.5.0 codename Knife)
- >> Maximum connections set to 1024
- >> Listening on 0.0.0.0:4567, CTRL+C to stop
+ %legend Start Gollum And Edit Locally:
+ %pre.dark
+ :preserve
+ gollum
+ == Sinatra/1.3.5 has taken the stage on 4567 for development with backup from Thin
+ >> Thin web server (v1.5.0 codename Knife)
+ >> Maximum connections set to 1024
+ >> Listening on 0.0.0.0:4567, CTRL+C to stop
diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml
index 45460ed9f41..4c0b14e2c42 100644
--- a/app/views/projects/wikis/history.html.haml
+++ b/app/views/projects/wikis/history.html.haml
@@ -1,37 +1,37 @@
- page_title "History", @page.title.capitalize, "Wiki"
= render 'nav'
+%div{ class: container_class }
+ .top-area
+ .nav-text
+ %strong
+ = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page)
+ %span.light
+ &middot;
+ History
-.top-area
- .nav-text
- %strong
- = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page)
- %span.light
- &middot;
- History
-
-.table-holder
- %table.table
- %thead
- %tr
- %th Page version
- %th Author
- %th Commit Message
- %th Last updated
- %th Format
- %tbody
- - @page.versions.each_with_index do |version, index|
- - commit = version
+ .table-holder
+ %table.table
+ %thead
%tr
- %td
- = link_to project_wiki_path_with_version(@project, @page,
- commit.id, index == 0) do
- = truncate_sha(commit.id)
- %td
- = commit.author.name
- %td
- = commit.message
- %td
- #{time_ago_with_tooltip(version.authored_date)}
- %td
- %strong
- = @page.page.wiki.page(@page.page.name, commit.id).try(:format)
+ %th Page version
+ %th Author
+ %th Commit Message
+ %th Last updated
+ %th Format
+ %tbody
+ - @page.versions.each_with_index do |version, index|
+ - commit = version
+ %tr
+ %td
+ = link_to project_wiki_path_with_version(@project, @page,
+ commit.id, index == 0) do
+ = truncate_sha(commit.id)
+ %td
+ = commit.author.name
+ %td
+ = commit.message
+ %td
+ #{time_ago_with_tooltip(version.authored_date)}
+ %td
+ %strong
+ = @page.page.wiki.page(@page.page.name, commit.id).try(:format)
diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml
index 2f6162fa3c5..9c10acd4cb6 100644
--- a/app/views/projects/wikis/pages.html.haml
+++ b/app/views/projects/wikis/pages.html.haml
@@ -1,12 +1,14 @@
+- @no_container = true
- page_title "Pages", "Wiki"
= render 'nav'
-%ul.content-list
- - @wiki_pages.each do |wiki_page|
- %li
- = link_to wiki_page.title, namespace_project_wiki_path(@project.namespace, @project, wiki_page)
- %small (#{wiki_page.format})
- .pull-right
- %small Last edited #{time_ago_with_tooltip(wiki_page.commit.authored_date)}
-= paginate @wiki_pages, theme: 'gitlab'
+%div{ class: container_class }
+ %ul.content-list
+ - @wiki_pages.each do |wiki_page|
+ %li
+ = link_to wiki_page.title, namespace_project_wiki_path(@project.namespace, @project, wiki_page)
+ %small (#{wiki_page.format})
+ .pull-right
+ %small Last edited #{time_ago_with_tooltip(wiki_page.commit.authored_date)}
+ = paginate @wiki_pages, theme: 'gitlab'
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index 9166c0edb3b..5cebb538cf5 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -1,24 +1,26 @@
+- @no_container = true
- page_title @page.title.capitalize, "Wiki"
= render 'nav'
-.top-area
- .nav-text
- %strong= @page.title.capitalize
+%div{ class: container_class }
+ .top-area
+ .nav-text
+ %strong= @page.title.capitalize
- %span.wiki-last-edit-by
- &middot;
- last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)}
+ %span.wiki-last-edit-by
+ &middot;
+ last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)}
- .nav-controls
- = render 'main_links'
+ .nav-controls
+ = render 'main_links'
-- if @page.historical?
- .warning_message
- This is an old version of this page.
- You can view the #{link_to "most recent version", namespace_project_wiki_path(@project.namespace, @project, @page)} or browse the #{link_to "history", namespace_project_wiki_history_path(@project.namespace, @project, @page)}.
+ - if @page.historical?
+ .warning_message
+ This is an old version of this page.
+ You can view the #{link_to "most recent version", namespace_project_wiki_path(@project.namespace, @project, @page)} or browse the #{link_to "history", namespace_project_wiki_history_path(@project.namespace, @project, @page)}.
-.wiki-holder.prepend-top-default.append-bottom-default
- .wiki
- = preserve do
- = render_wiki_content(@page)
+ .wiki-holder.prepend-top-default.append-bottom-default
+ .wiki
+ = preserve do
+ = render_wiki_content(@page)
diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml
index 0fe8a3b490a..290743feb4a 100644
--- a/app/views/search/results/_blob.html.haml
+++ b/app/views/search/results/_blob.html.haml
@@ -2,9 +2,10 @@
.blob-result
.file-holder
.file-title
- = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(blob.ref, blob.filename), :anchor => "L" + blob.startline.to_s) do
+ - blob_link = namespace_project_blob_path(@project.namespace, @project, tree_join(blob.ref, blob.filename))
+ = link_to blob_link do
%i.fa.fa-file
%strong
= blob.filename
.file-content.code.term
- = render 'shared/file_highlight', blob: blob, first_line_number: blob.startline
+ = render 'shared/file_highlight', blob: blob, first_line_number: blob.startline, blob_link: blob_link
diff --git a/app/views/shared/_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/_event_filter.html.haml b/app/views/shared/_event_filter.html.haml
index 30055002213..8824bcc158e 100644
--- a/app/views/shared/_event_filter.html.haml
+++ b/app/views/shared/_event_filter.html.haml
@@ -1,7 +1,5 @@
%ul.nav-links.event-filter.scrolling-tabs
- .fade-left
= event_filter_link EventFilter.push, 'Push events'
= event_filter_link EventFilter.merged, 'Merge events'
= event_filter_link EventFilter.comments, 'Comments'
= event_filter_link EventFilter.team, 'Team'
- .fade-right
diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml
index 37dcf39c062..e26693bf5b9 100644
--- a/app/views/shared/_file_highlight.html.haml
+++ b/app/views/shared/_file_highlight.html.haml
@@ -1,13 +1,16 @@
+- repository = nil unless local_assigns.key?(:repository)
+
.file-content.code.js-syntax-highlight
.line-numbers
- if blob.data.present?
- link_icon = icon('link')
+ - link = blob_link if defined?(blob_link)
- blob.data.each_line.each_with_index do |_, index|
- offset = defined?(first_line_number) ? first_line_number : 1
- i = index + offset
-# We're not using `link_to` because it is too slow once we get to thousands of lines.
- %a.diff-line-num{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i}
+ %a.diff-line-num{href: "#{link}#L#{i}", id: "L#{i}", 'data-line-number' => i}
= link_icon
= i
.blob-content{data: {blob_id: blob.id}}
- = highlight(blob.name, blob.data, plain: blob.no_highlighting?)
+ = highlight(blob.path, blob.data, repository: repository, plain: blob.no_highlighting?)
diff --git a/app/views/shared/_import_form.html.haml b/app/views/shared/_import_form.html.haml
index 627814bcfae..65a3a6bddab 100644
--- a/app/views/shared/_import_form.html.haml
+++ b/app/views/shared/_import_form.html.haml
@@ -2,7 +2,7 @@
= f.label :import_url, class: 'control-label' do
%span Git repository URL
.col-sm-10
- = f.text_field :import_url, class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git'
+ = f.text_field :import_url, class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git', disabled: true
.well.prepend-top-20
%ul
diff --git a/app/views/shared/_labels_row.html.haml b/app/views/shared/_labels_row.html.haml
index 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/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml
index eb2e1919e19..ea7162d4d63 100644
--- a/app/views/shared/_ref_switcher.html.haml
+++ b/app/views/shared/_ref_switcher.html.haml
@@ -1,7 +1,14 @@
+- dropdown_toggle_text = @ref || @project.default_branch
= form_tag switch_namespace_project_refs_path(@project.namespace, @project), method: :get, class: "project-refs-form" do
- = select_tag "ref", grouped_options_refs, class: "project-refs-select select2 select2-sm"
= hidden_field_tag :destination, destination
- if defined?(path)
= hidden_field_tag :path, path
- @options && @options.each do |key, value|
= hidden_field_tag key, value, id: nil
+ .dropdown
+ = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_namespace_project_path(@project.namespace, @project) }, { toggle_class: "js-project-refs-dropdown" }
+ .dropdown-menu.dropdown-menu-selectable{ class: ("dropdown-menu-align-right" if local_assigns[:align_right]) }
+ = dropdown_title "Switch branch/tag"
+ = dropdown_filter "Search branches and tags"
+ = dropdown_content
+ = dropdown_loading
diff --git a/app/views/shared/_visibility_level.html.haml b/app/views/shared/_visibility_level.html.haml
index 1c6ec198d3d..107ad19177c 100644
--- a/app/views/shared/_visibility_level.html.haml
+++ b/app/views/shared/_visibility_level.html.haml
@@ -1,7 +1,7 @@
.form-group.project-visibility-level-holder
= f.label :visibility_level, class: 'control-label' do
Visibility Level
- = link_to "(?)", help_page_path("public_access", "public_access")
+ = link_to "(?)", help_page_path("public_access/public_access")
.col-sm-10
- if can_change_visibility_level
= render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: visibility_level, form_model: form_model)
diff --git a/app/views/shared/icons/_group.svg b/app/views/shared/icons/_group.svg.erb
index 75cae0d16c8..53635016900 100644
--- a/app/views/shared/icons/_group.svg
+++ b/app/views/shared/icons/_group.svg.erb
@@ -1,9 +1,4 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
- <!-- Generator: Sketch 3.7.2 (28276) - http://www.bohemiancoding.com/sketch -->
- <title>Group</title>
- <desc>Created with Sketch.</desc>
- <defs></defs>
+<svg width="<%= size %>" height="<%= size %>" viewBox="0 0 16 16">
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Group" fill="#303030">
<path d="M15.6667,10.0105 L10.3337,10.0105 C10.1497,10.0105 9.9997,10.1775 9.9997,10.3845 L9.9997,15.6145 C9.9997,15.8215 10.1497,15.9885 10.3337,15.9885 L15.6667,15.9885 C15.8507,15.9885 15.9997,15.8215 15.9997,15.6145 L15.9997,10.3845 C15.9997,10.1775 15.8507,10.0105 15.6667,10.0105 L15.6667,10.0105 L15.6667,10.0105 Z M11.9997,14.0105 L13.9997,14.0105 L13.9997,12.0105 L11.9997,12.0105 L11.9997,14.0105 L11.9997,14.0105 Z" id="Fill-11"></path>
@@ -15,4 +10,4 @@
<path d="M11.6667,6.21724894e-15 L4.3337,6.21724894e-15 C4.1497,6.21724894e-15 3.9997,0.167 3.9997,0.374 L3.9997,6.604 C3.9997,6.811 4.1497,6.978 4.3337,6.978 L11.6667,6.978 C11.8507,6.978 11.9997,6.811 11.9997,6.604 L11.9997,0.374 C11.9997,0.167 11.8507,6.21724894e-15 11.6667,6.21724894e-15 L11.6667,6.21724894e-15 L11.6667,6.21724894e-15 Z M5.9997,5 L9.9997,5 L9.9997,2 L5.9997,2 L5.9997,5 L5.9997,5 Z" id="Fill-14"></path>
</g>
</g>
-</svg> \ No newline at end of file
+</svg>
diff --git a/app/views/shared/icons/_icon_commit.svg b/app/views/shared/icons/_icon_commit.svg
new file mode 100644
index 00000000000..0e96035b7b7
--- /dev/null
+++ b/app/views/shared/icons/_icon_commit.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40">
+ <path fill="#8F8F8F" fill-rule="evenodd" d="M28.7769836,18 C27.8675252,13.9920226 24.2831748,11 20,11 C15.7168252,11 12.1324748,13.9920226 11.2230164,18 L4.0085302,18 C2.90195036,18 2,18.8954305 2,20 C2,21.1122704 2.8992496,22 4.0085302,22 L11.2230164,22 C12.1324748,26.0079774 15.7168252,29 20,29 C24.2831748,29 27.8675252,26.0079774 28.7769836,22 L35.9914698,22 C37.0980496,22 38,21.1045695 38,20 C38,18.8877296 37.1007504,18 35.9914698,18 L28.7769836,18 L28.7769836,18 Z M20,25 C22.7614237,25 25,22.7614237 25,20 C25,17.2385763 22.7614237,15 20,15 C17.2385763,15 15,17.2385763 15,20 C15,22.7614237 17.2385763,25 20,25 L20,25 Z"/>
+</svg>
diff --git a/app/views/shared/icons/_icon_fork.svg b/app/views/shared/icons/_icon_fork.svg
new file mode 100644
index 00000000000..420ffe3a55b
--- /dev/null
+++ b/app/views/shared/icons/_icon_fork.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><circle id="a" cx="4" cy="4" r="4"/><mask id="d" width="8" height="8" x="0" y="0" fill="#fff"><use xlink:href="#a"/></mask><circle id="b" cx="20" cy="4" r="4"/><mask id="e" width="8" height="8" x="0" y="0" fill="#fff"><use xlink:href="#b"/></mask><circle id="c" cx="12" cy="30" r="4"/><mask id="f" width="8" height="8" x="0" y="0" fill="#fff"><use xlink:href="#c"/></mask></defs><g fill="none" fill-rule="evenodd" transform="translate(8 3)"><path fill="#7E7E7E" d="M10 19.667c-4.14-1.29-7.389-5.878-7.389-5.878C2.274 13.353 2 12.545 2 12.01V6h4v5.509c0 .276.166.65.367.831 0 0 1.136 1.028 1.746 1.574C9.617 15.261 11.048 16 12.09 16c1.028 0 2.41-.723 3.858-2.048.588-.54 1.84-1.742 1.84-1.742a.784.784 0 0 0 .211-.502V6h4v6.008c0 .548-.259 1.349-.601 1.795 0 0-3.21 4.707-7.399 5.916V27h-4v-7.333z"/><use stroke="#7E7E7E" stroke-width="4" mask="url(#d)" xlink:href="#a"/><use stroke="#7E7E7E" stroke-width="4" mask="url(#e)" xlink:href="#b"/><use stroke="#7E7E7E" stroke-width="4" mask="url(#f)" xlink:href="#c"/></g></svg> \ No newline at end of file
diff --git a/app/views/shared/icons/_icon_status_cancel.svg b/app/views/shared/icons/_icon_status_cancel.svg
new file mode 100644
index 00000000000..6a0bc1490c4
--- /dev/null
+++ b/app/views/shared/icons/_icon_status_cancel.svg
@@ -0,0 +1,12 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <circle id="a" cx="7" cy="7" r="7"/>
+ <mask id="b" width="14" height="14" x="0" y="0" fill="white">
+ <use xlink:href="#a"/>
+ </mask>
+ </defs>
+ <g fill="none" fill-rule="evenodd">
+ <use stroke="#5C5C5C" stroke-width="2" mask="url(#b)" xlink:href="#a"/>
+ <rect width="10" height="1" x="2" y="6.5" fill="#5C5C5C" transform="rotate(45 7 7)" rx=".3"/>
+ </g>
+</svg>
diff --git a/app/views/shared/icons/_icon_status_failed.svg b/app/views/shared/icons/_icon_status_failed.svg
new file mode 100644
index 00000000000..c41ca18cae7
--- /dev/null
+++ b/app/views/shared/icons/_icon_status_failed.svg
@@ -0,0 +1,12 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <circle id="a" cx="7" cy="7" r="7"/>
+ <mask id="b" width="14" height="14" x="0" y="0" fill="white">
+ <use xlink:href="#a"/>
+ </mask>
+ </defs>
+ <g fill="none" fill-rule="evenodd">
+ <use stroke="#D22852" stroke-width="2" mask="url(#b)" xlink:href="#a"/>
+ <path fill="#D22852" d="M7.5,6.5 L7.5,4.30578971 C7.5,4.12531853 7.36809219,4 7.20537567,4 L6.79462433,4 C6.63904572,4 6.5,4.13690672 6.5,4.30578971 L6.5,6.5 L4.30578971,6.5 C4.12531853,6.5 4,6.63190781 4,6.79462433 L4,7.20537567 C4,7.36095428 4.13690672,7.5 4.30578971,7.5 L6.5,7.5 L6.5,9.69421029 C6.5,9.87468147 6.63190781,10 6.79462433,10 L7.20537567,10 C7.36095428,10 7.5,9.86309328 7.5,9.69421029 L7.5,7.5 L9.69421029,7.5 C9.87468147,7.5 10,7.36809219 10,7.20537567 L10,6.79462433 C10,6.63904572 9.86309328,6.5 9.69421029,6.5 L7.5,6.5 Z" transform="rotate(45 7 7)"/>
+ </g>
+</svg>
diff --git a/app/views/shared/icons/_icon_status_pending.svg b/app/views/shared/icons/_icon_status_pending.svg
new file mode 100644
index 00000000000..035cd8b4ccc
--- /dev/null
+++ b/app/views/shared/icons/_icon_status_pending.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <circle id="a" cx="7" cy="7" r="7"/>
+ <mask id="b" width="14" height="14" x="0" y="0" fill="white">
+ <use xlink:href="#a"/>
+ </mask>
+ </defs>
+ <g fill="none" fill-rule="evenodd">
+ <use stroke="#E75E40" stroke-width="2" mask="url(#b)" xlink:href="#a"/>
+ <rect width="1" height="4" x="5" y="5" fill="#E75E40" rx=".3"/>
+ <rect width="1" height="4" x="8" y="5" fill="#E75E40" rx=".3"/>
+ </g>
+</svg>
diff --git a/app/views/shared/icons/_icon_status_running.svg b/app/views/shared/icons/_icon_status_running.svg
new file mode 100644
index 00000000000..a48b3a25099
--- /dev/null
+++ b/app/views/shared/icons/_icon_status_running.svg
@@ -0,0 +1,12 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <circle id="a" cx="7" cy="7" r="7"/>
+ <mask id="b" width="14" height="14" x="0" y="0" fill="white">
+ <use xlink:href="#a"/>
+ </mask>
+ </defs>
+ <g fill="none" fill-rule="evenodd">
+ <use stroke="#2D9FD8" stroke-width="2" mask="url(#b)" xlink:href="#a"/>
+ <path fill="#2D9FD8" d="M7,3.00800862 C9.09023405,3.13960661 10.7448145,4.87657932 10.7448145,7 C10.7448145,9.209139 8.95395346,11 6.74481446,11 C5.4560962,11 4.30972054,10.3905589 3.57817301,9.44416214 L7,7 L7,3.00800862 Z"/>
+ </g>
+</svg>
diff --git a/app/views/shared/icons/_icon_status_success.svg b/app/views/shared/icons/_icon_status_success.svg
new file mode 100644
index 00000000000..260eab013a3
--- /dev/null
+++ b/app/views/shared/icons/_icon_status_success.svg
@@ -0,0 +1,15 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <circle id="a" cx="7" cy="7" r="7"/>
+ <mask id="b" width="14" height="14" x="0" y="0" fill="white">
+ <use xlink:href="#a"/>
+ </mask>
+ </defs>
+ <g fill="none" fill-rule="evenodd">
+ <use stroke="#31AF64" stroke-width="2" mask="url(#b)" xlink:href="#a"/>
+ <g fill="#31AF64" transform="rotate(45 -.13 10.953)">
+ <rect width="1" height="5" x="2" rx=".3"/>
+ <rect width="3" height="1" y="4" rx=".3"/>
+ </g>
+ </g>
+</svg>
diff --git a/app/views/shared/icons/_icon_status_warning.svg b/app/views/shared/icons/_icon_status_warning.svg
new file mode 100644
index 00000000000..d47e7a1c93f
--- /dev/null
+++ b/app/views/shared/icons/_icon_status_warning.svg
@@ -0,0 +1,15 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs>
+ <circle id="a" cx="7" cy="7" r="7"/>
+ <mask id="b" width="14" height="14" x="0" y="0" fill="white">
+ <use xlink:href="#a"/>
+ </mask>
+ </defs>
+ <g fill="none" fill-rule="evenodd">
+ <g fill="#FF8A24" transform="translate(6 3)">
+ <rect width="2" height="5" rx=".5"/>
+ <rect width="2" height="2" y="6" rx=".5"/>
+ </g>
+ <use stroke="#FF8A24" stroke-width="2" mask="url(#b)" xlink:href="#a"/>
+ </g>
+</svg>
diff --git a/app/views/shared/icons/_icon_timer.svg b/app/views/shared/icons/_icon_timer.svg
new file mode 100644
index 00000000000..0b1e5804427
--- /dev/null
+++ b/app/views/shared/icons/_icon_timer.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40"><g fill="#8F8F8F" fill-rule="evenodd"><path d="M29.513 10.134A15.922 15.922 0 0 0 23 7.28V6h2.993C26.55 6 27 5.552 27 5V2a1 1 0 0 0-1.007-1H14.007C13.45 1 13 1.448 13 2v3a1 1 0 0 0 1.007 1H17v1.28C9.597 8.686 4 15.19 4 23c0 8.837 7.163 16 16 16s16-7.163 16-16c0-3.461-1.099-6.665-2.967-9.283l1.327-1.58a2.498 2.498 0 0 0-.303-3.53 2.499 2.499 0 0 0-3.528.315l-1.016 1.212zM20 34c6.075 0 11-4.925 11-11s-4.925-11-11-11S9 16.925 9 23s4.925 11 11 11z"/><path d="M19 21h-4.002c-.552 0-.998.452-.998 1.01v1.98c0 .567.447 1.01.998 1.01h7.004c.274 0 .521-.111.701-.291a.979.979 0 0 0 .297-.704v-8.01c0-.54-.452-.995-1.01-.995h-1.98a.997.997 0 0 0-1.01.995V21z"/></g></svg> \ No newline at end of file
diff --git a/app/views/shared/icons/_issues.svg b/app/views/shared/icons/_issues.svg
deleted file mode 100644
index 2682c27ade9..00000000000
--- a/app/views/shared/icons/_issues.svg
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
- <!-- Generator: Sketch 3.7.2 (28276) - http://www.bohemiancoding.com/sketch -->
- <title>Group</title>
- <desc>Created with Sketch.</desc>
- <defs></defs>
- <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
- <g id="Group" fill="#7E7C7C">
- <path d="M8,0 C3.581,0 0,3.581 0,8 C0,12.419 3.581,16 8,16 C12.419,16 16,12.419 16,8 C16,3.581 12.419,0 8,0 M8,2 C11.308,2 14,4.692 14,8 C14,11.308 11.308,14 8,14 C4.692,14 2,11.308 2,8 C2,4.692 4.692,2 8,2" id="Fill-1"></path>
- <path d="M7.1597,4 L8.8887,4 L8.8887,8 L7.1107,8 L7.1597,4 Z M7.1597,9.6667 L8.8887,9.6667 L8.8887,11.4447 L7.1107,11.4447 L7.1597,9.6667 Z" id="Combined-Shape"></path>
- </g>
- </g>
-</svg> \ No newline at end of file
diff --git a/app/views/shared/icons/_issues.svg.erb b/app/views/shared/icons/_issues.svg.erb
new file mode 100644
index 00000000000..fa8655b5609
--- /dev/null
+++ b/app/views/shared/icons/_issues.svg.erb
@@ -0,0 +1,4 @@
+<svg width="<%= size %>" height="<%= size %>" viewBox="0 0 16 16" class="gitlab-icon">
+ <path fill="#7E7C7C" d="M8,0 C3.581,0 0,3.581 0,8 C0,12.419 3.581,16 8,16 C12.419,16 16,12.419 16,8 C16,3.581 12.419,0 8,0 M8,2 C11.308,2 14,4.692 14,8 C14,11.308 11.308,14 8,14 C4.692,14 2,11.308 2,8 C2,4.692 4.692,2 8,2"></path>
+ <path fill="#7E7C7C" d="M7.1597,4 L8.8887,4 L8.8887,8 L7.1107,8 L7.1597,4 Z M7.1597,9.6667 L8.8887,9.6667 L8.8887,11.4447 L7.1107,11.4447 L7.1597,9.6667 Z"></path>
+</svg>
diff --git a/app/views/shared/icons/_project.svg b/app/views/shared/icons/_project.svg
deleted file mode 100644
index 1e8b43f8c6b..00000000000
--- a/app/views/shared/icons/_project.svg
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
- <!-- Generator: Sketch 3.8.3 (29802) - http://www.bohemiancoding.com/sketch -->
- <title>Page 1</title>
- <desc>Created with Sketch.</desc>
- <defs></defs>
- <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
- <path d="M6,6 L12,6 L12,5 L6,5 L6,6 Z M6,8 L12,8 L12,7 L6,7 L6,8 Z M6,10 L12,10 L12,9 L6,9 L6,10 Z M6,12 L12,12 L12,11 L6,11 L6,12 Z M4,6 L5,6 L5,5 L4,5 L4,6 Z M4,8 L5,8 L5,7 L4,7 L4,8 Z M4,10 L5,10 L5,9 L4,9 L4,10 Z M4,12 L5,12 L5,11 L4,11 L4,12 Z M13,3 L10,3 L10,4 L6,4 L6,3 L3,3 L3,13 L13,13 L13,3 Z M2,14 L14,14 L14,2 L2,2 L2,14 Z M1,0 C0.448,0 0,0.448 0,1 L0,15 C0,15.552 0.448,16 1,16 L15,16 C15.552,16 16,15.552 16,15 L16,1 C16,0.448 15.552,0 15,0 L1,0 Z" fill="#7F7E7E"></path>
- </g>
-</svg> \ No newline at end of file
diff --git a/app/views/shared/icons/_project.svg.erb b/app/views/shared/icons/_project.svg.erb
new file mode 100644
index 00000000000..2f60bb7245e
--- /dev/null
+++ b/app/views/shared/icons/_project.svg.erb
@@ -0,0 +1,3 @@
+<svg width="<%= size %>" height="<%= size %>" viewBox="0 0 16 16">
+ <path d="M6,6 L12,6 L12,5 L6,5 L6,6 Z M6,8 L12,8 L12,7 L6,7 L6,8 Z M6,10 L12,10 L12,9 L6,9 L6,10 Z M6,12 L12,12 L12,11 L6,11 L6,12 Z M4,6 L5,6 L5,5 L4,5 L4,6 Z M4,8 L5,8 L5,7 L4,7 L4,8 Z M4,10 L5,10 L5,9 L4,9 L4,10 Z M4,12 L5,12 L5,11 L4,11 L4,12 Z M13,3 L10,3 L10,4 L6,4 L6,3 L3,3 L3,13 L13,13 L13,3 Z M2,14 L14,14 L14,2 L2,2 L2,14 Z M1,0 C0.448,0 0,0.448 0,1 L0,15 C0,15.552 0.448,16 1,16 L15,16 C15.552,16 16,15.552 16,15 L16,1 C16,0.448 15.552,0 15,0 L1,0 Z" fill="#7F7E7E" fill-rule="evenodd"></path>
+</svg>
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index 094d6636c66..0b7fa8c7d06 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -44,9 +44,15 @@
placeholder: "Search authors", data: { first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: @project.id, field_name: "update[assignee_id]" } })
.filter-item.inline
= dropdown_tag("Milestone", options: { title: "Assign milestone", toggle_class: 'js-milestone-select js-extra-options js-filter-submit js-filter-bulk-update', filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", placeholder: "Search milestones", data: { show_no: true, field_name: "update[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), use_id: true } })
-
.filter-item.inline.labels-filter
= render "shared/issuable/label_dropdown", classes: ['js-filter-bulk-update', 'js-multiselect'], show_create: false, show_footer: false, extra_options: false, filter_submit: false, show_footer: false, data_options: { persist_when_hide: "true", field_name: "update[label_ids][]", show_no: false, show_any: false, use_id: true }
+ .filter-item.inline
+ = dropdown_tag("Subscription", options: { toggle_class: "js-subscription-event", title: "Change subscription", dropdown_class: "dropdown-menu-selectable", data: { field_name: "update[subscription_event]" } } ) do
+ %ul
+ %li
+ %a{href: "#", data: {id: "subscribe"}} Subscribe
+ %li
+ %a{href: "#", data: {id: "unsubscribe"}} Unsubscribe
= hidden_field_tag 'update[issues_ids]', []
= hidden_field_tag :state_event, params[:state_event]
@@ -63,6 +69,7 @@
new LabelsSelect();
new MilestoneSelect();
new IssueStatusSelect();
+ new SubscriptionSelect();
$('form.filter-form').on('submit', function (event) {
event.preventDefault();
Turbolinks.visit(this.action + '&' + $(this).serialize());
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index adfab1af53e..e020a7d4d00 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -19,7 +19,7 @@
= form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, format: :json, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f|
.block.assignee
- .sidebar-collapsed-icon.sidebar-collapsed-user{data: {toggle: "tooltip", placement: "left", container: "body"}, title: (issuable.assignee.to_reference if issuable.assignee)}
+ .sidebar-collapsed-icon.sidebar-collapsed-user{data: {toggle: "tooltip", placement: "left", container: "body"}, title: (issuable.assignee.name if issuable.assignee)}
- if issuable.assignee
= link_to_member(@project, issuable.assignee, size: 24)
- else
diff --git a/app/views/shared/members/_access_request_buttons.html.haml b/app/views/shared/members/_access_request_buttons.html.haml
index 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?
- &nbsp;
= 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)
- &nbsp;
- 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 b5963876034..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
- %small= "(#{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/milestones/_issuable.html.haml b/app/views/shared/milestones/_issuable.html.haml
index 47b66d44e43..3c03c220ddd 100644
--- a/app/views/shared/milestones/_issuable.html.haml
+++ b/app/views/shared/milestones/_issuable.html.haml
@@ -21,7 +21,8 @@
= link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, label_name: label.title, state: 'all' }) do
- render_colored_label(label)
- - if assignee
- = link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, assignee_id: issuable.assignee_id, state: 'all' }),
- class: 'has-tooltip', title: "Assigned to #{assignee.name}", data: { container: 'body' } do
- - image_tag(avatar_icon(issuable.assignee, 16), class: "avatar s16", alt: '')
+ %span{ class: "assignee-icon" }
+ - if assignee
+ = link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, assignee_id: issuable.assignee_id, state: 'all' }),
+ class: 'has-tooltip', title: "Assigned to #{assignee.name}", data: { container: 'body' } do
+ - image_tag(avatar_icon(issuable.assignee, 16), class: "avatar s16", alt: '')
diff --git a/app/views/shared/milestones/_summary.html.haml b/app/views/shared/milestones/_summary.html.haml
index 975c74f4ea6..dee2472fa79 100644
--- a/app/views/shared/milestones/_summary.html.haml
+++ b/app/views/shared/milestones/_summary.html.haml
@@ -26,7 +26,6 @@
%span.pull-right.tab-issues-buttons
- if project && can?(current_user, :create_issue, project)
= link_to new_namespace_project_issue_path(project.namespace, project, issue: { milestone_id: milestone.id }), class: "btn btn-grouped", title: "New Issue" do
- %i.fa.fa-plus
New Issue
= link_to 'Browse Issues', milestones_browse_issuables_path(milestone, type: :issues), class: "btn btn-grouped"
%span.pull-right.tab-merge-requests-buttons.hidden
diff --git a/app/views/shared/projects/_dropdown.html.haml b/app/views/shared/projects/_dropdown.html.haml
index 1169bed0382..b7f8551153b 100644
--- a/app/views/shared/projects/_dropdown.html.haml
+++ b/app/views/shared/projects/_dropdown.html.haml
@@ -1,31 +1,30 @@
- @sort ||= sort_value_recently_updated
- personal = params[:personal]
- archived = params[:archived]
+- namespace_id = params[:namespace_id]
.dropdown.inline
- %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
- %span.light
- = projects_sort_options_hash[@sort]
- %b.caret
+ - toggle_text = projects_sort_options_hash[@sort]
+ = dropdown_toggle(toggle_text, { toggle: 'dropdown' }, { id: 'sort-projects-dropdown' })
%ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable
%li.dropdown-header
Sort by
- projects_sort_options_hash.each do |value, title|
%li
- = link_to filter_projects_path(sort: value, archived: archived, personal: personal), class: ("is-active" if @sort == value) do
+ = link_to filter_projects_path(namespace_id: namespace_id, sort: value, archived: archived, personal: personal), class: ("is-active" if @sort == value) do
= title
%li.divider
%li
- = link_to filter_projects_path(sort: @sort, archived: nil), class: ("is-active" unless params[:archived].present?) do
+ = link_to filter_projects_path(namespace_id: namespace_id, sort: @sort, archived: nil), class: ("is-active" unless params[:archived].present?) do
Hide archived projects
%li
- = link_to filter_projects_path(sort: @sort, archived: true), class: ("is-active" if params[:archived].present?) do
+ = link_to filter_projects_path(namespace_id: namespace_id, sort: @sort, archived: true), class: ("is-active" if params[:archived].present?) do
Show archived projects
- if current_user
%li.divider
%li
- = link_to filter_projects_path(sort: @sort, personal: nil), class: ("is-active" unless personal) do
+ = link_to filter_projects_path(namespace_id: namespace_id, sort: @sort, personal: nil), class: ("is-active" unless personal.present?) do
Owned by anyone
%li
- = link_to filter_projects_path(sort: @sort, personal: true), class: ("is-active" if personal) do
+ = link_to filter_projects_path(namespace_id: namespace_id, sort: @sort, personal: true), class: ("is-active" if personal.present?) do
Owned by me
diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml
index 2e08bb2ac08..3a9dd37dc7d 100644
--- a/app/views/shared/projects/_list.html.haml
+++ b/app/views/shared/projects/_list.html.haml
@@ -16,6 +16,12 @@
= render "shared/projects/project", project: project, skip_namespace: skip_namespace,
avatar: avatar, stars: stars, css_class: css_class, ci: ci, use_creator_avatar: use_creator_avatar,
forks: forks, show_last_commit_as_description: show_last_commit_as_description
+
+ - if @private_forks_count && @private_forks_count > 0
+ %li.project-row.private-forks-notice
+ = icon('lock fw', base: 'circle', class: 'fa-lg private-fork-icon')
+ %strong= pluralize(@private_forks_count, 'private fork')
+ %span you have no access to.
= paginate(projects, remote: remote, theme: "gitlab") if projects.respond_to? :total_pages
- else
.nothing-here-block No projects found
diff --git a/app/views/shared/web_hooks/_form.html.haml b/app/views/shared/web_hooks/_form.html.haml
index d1e861ca80c..2585ed9360b 100644
--- a/app/views/shared/web_hooks/_form.html.haml
+++ b/app/views/shared/web_hooks/_form.html.haml
@@ -6,7 +6,7 @@
%h4.prepend-top-0
= page_title
%p
- #{link_to "Webhooks", help_page_path("web_hooks", "web_hooks")} can be
+ #{link_to "Webhooks", help_page_path("web_hooks/web_hooks")} can be
used for binding events when something is happening within the project.
.col-lg-9.append-bottom-default
= form_for hook, as: :hook, url: polymorphic_path(url_components + [:hooks]) do |f|
diff --git a/app/views/snippets/_actions.html.haml b/app/views/snippets/_actions.html.haml
index a7769654b61..160c6cd84da 100644
--- a/app/views/snippets/_actions.html.haml
+++ b/app/views/snippets/_actions.html.haml
@@ -1,27 +1,28 @@
.hidden-xs
- = link_to new_snippet_path, class: "btn btn-grouped btn-create new-snippet-link", title: "New Snippet" do
- = icon('plus')
- New Snippet
+ - if current_user
+ = link_to new_snippet_path, class: "btn btn-grouped btn-create new-snippet-link", title: "New Snippet" do
+ New Snippet
- if can?(current_user, :update_personal_snippet, @snippet)
= link_to edit_snippet_path(@snippet), class: "btn btn-grouped snippable-edit" do
Edit
- if can?(current_user, :admin_personal_snippet, @snippet)
- = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-warning", title: 'Delete Snippet' do
+ = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do
Delete
-.visible-xs-block.dropdown
- %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
- Options
- %span.caret
- .dropdown-menu.dropdown-menu-full-width
- %ul
- %li
- = link_to new_snippet_path, title: "New Snippet" do
- New Snippet
- - if can?(current_user, :update_personal_snippet, @snippet)
+- if current_user
+ .visible-xs-block.dropdown
+ %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
+ Options
+ %span.caret
+ .dropdown-menu.dropdown-menu-full-width
+ %ul
%li
- = link_to edit_snippet_path(@snippet) do
- Edit
- - if can?(current_user, :admin_personal_snippet, @snippet)
- %li
- = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
- Delete
+ = link_to new_snippet_path, title: "New Snippet" do
+ New Snippet
+ - if can?(current_user, :update_personal_snippet, @snippet)
+ %li
+ = link_to edit_snippet_path(@snippet) do
+ Edit
+ - if can?(current_user, :admin_personal_snippet, @snippet)
+ %li
+ = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
+ Delete
diff --git a/app/views/users/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 92305594a81..db2b4885861 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -1,6 +1,8 @@
- page_title @user.name
- page_description @user.bio
-- page_specific_javascripts asset_path("users/application.js")
+- content_for :page_specific_javascripts do
+ = page_specific_javascript_tag('lib/d3.js')
+ = page_specific_javascript_tag('users/application.js')
- header_title @user.name, user_path(@user)
- @no_container = true
@@ -27,6 +29,11 @@
&nbsp;
= link_to user_path(@user, :atom, { private_token: current_user.private_token }), class: 'btn btn-gray' do
= icon('rss')
+ - if current_user.admin?
+ &nbsp;
+ = link_to [:admin, @user], class: 'btn btn-gray', title: 'View user in admin area',
+ data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
+ = icon('users')
.avatar-holder
= link_to avatar_icon(@user, 400), target: '_blank' do
diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb
index 971f969e25e..8551288e2f2 100644
--- a/app/workers/emails_on_push_worker.rb
+++ b/app/workers/emails_on_push_worker.rb
@@ -28,18 +28,30 @@ class EmailsOnPushWorker
:push
end
+ merge_base_sha = project.merge_base_commit(before_sha, after_sha).try(:sha)
+
diff_refs = nil
compare = nil
reverse_compare = false
if action == :push
compare = Gitlab::Git::Compare.new(project.repository.raw_repository, before_sha, after_sha)
- diff_refs = [project.merge_base_commit(before_sha, after_sha), project.commit(after_sha)]
+
+ diff_refs = Gitlab::Diff::DiffRefs.new(
+ base_sha: merge_base_sha,
+ start_sha: before_sha,
+ head_sha: after_sha
+ )
return false if compare.same
if compare.commits.empty?
compare = Gitlab::Git::Compare.new(project.repository.raw_repository, after_sha, before_sha)
- diff_refs = [project.merge_base_commit(after_sha, before_sha), project.commit(before_sha)]
+
+ diff_refs = Gitlab::Diff::DiffRefs.new(
+ base_sha: merge_base_sha,
+ start_sha: after_sha,
+ head_sha: before_sha
+ )
reverse_compare = true
diff --git a/app/workers/git_garbage_collect_worker.rb b/app/workers/git_garbage_collect_worker.rb
new file mode 100644
index 00000000000..a6cefd4d601
--- /dev/null
+++ b/app/workers/git_garbage_collect_worker.rb
@@ -0,0 +1,16 @@
+class GitGarbageCollectWorker
+ include Sidekiq::Worker
+ include Gitlab::ShellAdapter
+
+ sidekiq_options queue: :gitlab_shell, retry: false
+
+ def perform(project_id)
+ project = Project.find(project_id)
+
+ gitlab_shell.gc(project.repository_storage_path, project.path_with_namespace)
+ # Refresh the branch cache in case garbage collection caused a ref lookup to fail
+ project.repository.after_create_branch
+ project.repository.branch_names
+ project.repository.has_visible_content?
+ end
+end
diff --git a/app/workers/gitlab_shell_one_shot_worker.rb b/app/workers/gitlab_shell_one_shot_worker.rb
deleted file mode 100644
index 4ddbcf574d5..00000000000
--- a/app/workers/gitlab_shell_one_shot_worker.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-class GitlabShellOneShotWorker
- include Sidekiq::Worker
- include Gitlab::ShellAdapter
-
- sidekiq_options queue: :gitlab_shell, retry: false
-
- def perform(action, *arg)
- gitlab_shell.send(action, *arg)
- end
-end
diff --git a/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/project_export_worker.rb b/app/workers/project_export_worker.rb
index 39f6037e077..615311e63f5 100644
--- a/app/workers/project_export_worker.rb
+++ b/app/workers/project_export_worker.rb
@@ -1,7 +1,7 @@
class ProjectExportWorker
include Sidekiq::Worker
- sidekiq_options queue: :gitlab_shell, retry: true
+ sidekiq_options queue: :gitlab_shell, retry: 3
def perform(current_user_id, project_id)
current_user = User.find(current_user_id)
diff --git a/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 05fec995ed3..50cc4235eda 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -76,7 +76,7 @@ module Gitlab
# Enable the asset pipeline
config.assets.enabled = true
- config.assets.paths << Gemojione.index.images_path
+ config.assets.paths << Gemojione.images_path
config.assets.precompile << "*.png"
config.assets.precompile << "print.css"
config.assets.precompile << "notify.css"
@@ -84,6 +84,10 @@ module Gitlab
config.assets.precompile << "graphs/application.js"
config.assets.precompile << "users/application.js"
config.assets.precompile << "network/application.js"
+ config.assets.precompile << "profile/application.js"
+ config.assets.precompile << "lib/utils/*.js"
+ config.assets.precompile << "lib/*.js"
+ config.assets.precompile << "u2f.js"
# Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0'
diff --git a/config/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
deleted file mode 100644
index 01c8dc5ff98..00000000000
--- a/config/gitlab.teatro.yml
+++ /dev/null
@@ -1,85 +0,0 @@
-
-production: &base
- gitlab:
- host: localhost
- port: 80
- https: false
-
- user: root
-
- email_from: example@example.com
-
- support_email: support@example.com
-
- default_projects_features:
- issues: true
- merge_requests: true
- wiki: true
- snippets: false
- visibility_level: "private" # can be "private" | "internal" | "public"
-
- issues_tracker:
-
- gravatar:
- enabled: true # Use user avatar image from Gravatar.com (default: true)
-
- ldap:
- enabled: false
- host: '_your_ldap_server'
- port: 636
- uid: 'sAMAccountName'
- method: 'ssl' # "tls" or "ssl" or "plain"
- bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
- password: '_the_password_of_the_bind_user'
- allow_username_or_email_login: true
-
- base: ''
-
- user_filter: ''
-
- omniauth:
- enabled: false
-
- satellites:
- # Relative paths are relative to Rails.root (default: tmp/repo_satellites/)
- path: /apps/gitlab-satellites/
-
- backup:
- path: "tmp/backups" # Relative paths are relative to Rails.root (default: tmp/backups/)
-
- 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
- receive_pack: true
-
- git:
- bin_path: /usr/bin/git
- max_size: 5242880 # 5.megabytes
- timeout: 10
-
- extra:
-
-development:
- <<: *base
-
-test:
- <<: *base
- gravatar:
- enabled: true
- gitlab:
- host: localhost
- port: 80
- issues_tracker:
- redmine:
- title: "Redmine"
- project_url: "http://redmine/projects/:issues_tracker_id"
- issues_url: "http://redmine/:project_id/:issues_tracker_id/:id"
- new_issue_url: "http://redmine/projects/:issues_tracker_id/issues/new"
-
-staging:
- <<: *base
diff --git a/config/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 09ffc319065..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?
@@ -215,10 +213,9 @@ Settings.gitlab.default_projects_features['container_registry'] = true if Settin
Settings.gitlab.default_projects_features['visibility_level'] = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE)
Settings.gitlab['repository_downloads_path'] = File.join(Settings.shared['path'], 'cache/archive') if Settings.gitlab['repository_downloads_path'].nil?
Settings.gitlab['restricted_signup_domains'] ||= []
-Settings.gitlab['import_sources'] ||= ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git']
+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/haml.rb b/config/initializers/haml.rb
deleted file mode 100644
index 1516476815a..00000000000
--- a/config/initializers/haml.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-Haml::Template.options[:ugly] = true
-
-# Remove the `:coffee` and `:coffeescript` filters
-#
-# See https://git.io/vztMu and http://stackoverflow.com/a/17571242/223897
-Haml::Filters.remove_filter('coffee')
-Haml::Filters.remove_filter('coffeescript')
diff --git a/config/initializers/hamlit.rb b/config/initializers/hamlit.rb
new file mode 100644
index 00000000000..7b545d8c06c
--- /dev/null
+++ b/config/initializers/hamlit.rb
@@ -0,0 +1,18 @@
+module Hamlit
+ class TemplateHandler
+ def call(template)
+ Engine.new(
+ generator: Temple::Generators::RailsOutputBuffer,
+ attr_quote: '"',
+ ).call(template.source)
+ end
+ end
+end
+
+ActionView::Template.register_template_handler(
+ :haml,
+ Hamlit::TemplateHandler.new,
+)
+
+Hamlit::Filters.remove_filter('coffee')
+Hamlit::Filters.remove_filter('coffeescript')
diff --git a/config/initializers/health_check.rb b/config/initializers/health_check.rb
index 79e2d23ab2e..4c91a61fb4a 100644
--- a/config/initializers/health_check.rb
+++ b/config/initializers/health_check.rb
@@ -1,3 +1,4 @@
HealthCheck.setup do |config|
config.standard_checks = ['database', 'migrations', 'cache']
+ config.full_checks = ['database', 'migrations', 'cache']
end
diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb
index d159f4eded2..c4266ab8ba5 100644
--- a/config/initializers/metrics.rb
+++ b/config/initializers/metrics.rb
@@ -113,6 +113,10 @@ if Gitlab::Metrics.enabled?
config.instrument_methods(Banzai::Renderer)
config.instrument_methods(Banzai::Querying)
+ config.instrument_instance_methods(Banzai::ObjectRenderer)
+ config.instrument_instance_methods(Banzai::Redactor)
+ config.instrument_methods(Banzai::NoteRenderer)
+
[Issuable, Mentionable, Participable].each do |klass|
config.instrument_instance_methods(klass)
config.instrument_instance_methods(klass::ClassMethods)
@@ -128,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/relative_naming_ci_namespace.rb b/config/initializers/relative_naming_ci_namespace.rb
new file mode 100644
index 00000000000..59abe1b9b91
--- /dev/null
+++ b/config/initializers/relative_naming_ci_namespace.rb
@@ -0,0 +1,16 @@
+# Description: https://coderwall.com/p/heed_q/rails-routing-and-namespaced-models
+#
+# This allows us to use CI ActiveRecord objects in all routes and use it:
+# - [project.namespace, project, build]
+#
+# instead of:
+# - namespace_project_build_path(project.namespace, project, build)
+#
+# Without that, Ci:: namespace is used for resolving routes:
+# - namespace_project_ci_build_path(project.namespace, project, build)
+
+module Ci
+ def self.use_relative_model_naming?
+ true
+ end
+end
diff --git a/config/initializers/secure_headers.rb b/config/initializers/secure_headers.rb
new file mode 100644
index 00000000000..9fd24a667cc
--- /dev/null
+++ b/config/initializers/secure_headers.rb
@@ -0,0 +1,109 @@
+# CSP headers have to have single quotes, so failures relating to quotes
+# inside Ruby string arrays are irrelevant.
+# rubocop:disable Lint/PercentStringArray
+require 'gitlab/current_settings'
+include Gitlab::CurrentSettings
+
+# If Sentry is enabled and the Rails app is running in production mode,
+# this will construct the Report URI for Sentry.
+if Rails.env.production? && current_application_settings.sentry_enabled
+ uri = URI.parse(current_application_settings.sentry_dsn)
+ CSP_REPORT_URI = "#{uri.scheme}://#{uri.host}/api#{uri.path}/csp-report/?sentry_key=#{uri.user}"
+else
+ CSP_REPORT_URI = ''
+end
+
+# Content Security Policy Headers
+# For more information on CSP see:
+# - https://gitlab.com/gitlab-org/gitlab-ce/issues/18231
+# - https://developer.mozilla.org/en-US/docs/Web/Security/CSP/CSP_policy_directives
+SecureHeaders::Configuration.default do |config|
+ # Mark all cookies as "Secure", "HttpOnly", and "SameSite=Strict".
+ config.cookies = {
+ secure: true,
+ httponly: true,
+ samesite: {
+ strict: true
+ }
+ }
+ config.x_content_type_options = "nosniff"
+ config.x_xss_protection = "1; mode=block"
+ config.x_download_options = "noopen"
+ config.x_permitted_cross_domain_policies = "none"
+ config.referrer_policy = "origin-when-cross-origin"
+ config.csp = {
+ # "Meta" values.
+ report_only: true,
+ preserve_schemes: true,
+
+ # "Directive" values.
+ # Default source allows nothing, more permissive values are set per-policy.
+ default_src: %w('none'),
+ # (Deprecated) Don't allow iframes.
+ frame_src: %w('none'),
+ # Only allow XMLHTTPRequests from the GitLab instance itself.
+ connect_src: %w('self'),
+ # Only load local fonts.
+ font_src: %w('self'),
+ # Load local images, any external image available over HTTPS.
+ img_src: %w(* 'self' data:),
+ # Audio and video can't be played on GitLab currently, so it's disabled.
+ media_src: %w('none'),
+ # Don't allow <object>, <embed>, or <applet> elements.
+ object_src: %w('none'),
+ # Allow local scripts and inline scripts.
+ script_src: %w('unsafe-inline' 'unsafe-eval' 'self'),
+ # Allow local stylesheets and inline styles.
+ style_src: %w('unsafe-inline' 'self'),
+ # The URIs that a user agent may use as the document base URL.
+ base_uri: %w('self'),
+ # Only allow local iframes and service workers
+ child_src: %w('self'),
+ # Only submit form information to the GitLab instance.
+ form_action: %w('self'),
+ # Disallow any parents from embedding a page in an iframe.
+ frame_ancestors: %w('none'),
+ # Don't allow any plugins (Flash, Shockwave, etc.)
+ plugin_types: %w(),
+ # Blocks all mixed (HTTP) content.
+ block_all_mixed_content: true,
+ # Upgrades insecure requests to HTTPS when possible.
+ upgrade_insecure_requests: true
+ }
+
+ # Reports are sent to Sentry if it's enabled.
+ if current_application_settings.sentry_enabled
+ config.csp[:report_uri] = %W(#{CSP_REPORT_URI})
+ end
+
+ # Allow Bootstrap Linter in development mode.
+ if Rails.env.development?
+ config.csp[:script_src] << "maxcdn.bootstrapcdn.com"
+ end
+
+ # reCAPTCHA
+ if current_application_settings.recaptcha_enabled
+ config.csp[:script_src] << "https://www.google.com/recaptcha/"
+ config.csp[:script_src] << "https://www.gstatic.com/recaptcha/"
+ config.csp[:frame_src] << "https://www.google.com/recaptcha/"
+ config.x_frame_options = "SAMEORIGIN"
+ end
+
+ # Gravatar
+ if current_application_settings.gravatar_enabled?
+ config.csp[:img_src] << "www.gravatar.com"
+ config.csp[:img_src] << "secure.gravatar.com"
+ config.csp[:img_src] << Gitlab.config.gravatar.host
+ end
+
+ # Piwik
+ if Gitlab.config.extra.has_key?('piwik_url') && Gitlab.config.extra.has_key?('piwik_site_id')
+ config.csp[:script_src] << Gitlab.config.extra.piwik_url
+ config.csp[:img_src] << Gitlab.config.extra.piwik_url
+ end
+
+ # Google Analytics
+ if Gitlab.config.extra.has_key?('google_analytics_id')
+ config.csp[:script_src] << "https://www.google-analytics.com"
+ end
+end
diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index f1eec674888..b40fd81ff96 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -13,7 +13,14 @@ 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_required_keys = %w(job_class cron)
+ cron_jobs.each do |k, v|
+ if cron_jobs[k] && cron_jobs_required_keys.all? { |s| cron_jobs[k].key?(s) }
+ cron_jobs[k]['class'] = cron_jobs[k].delete('job_class')
+ else
+ raise("Invalid cron_jobs config key: '#{k}'. Check your gitlab config file.")
+ end
+ end
Sidekiq::Cron::Job.load_from_hash! cron_jobs
# Database pool should be at least `sidekiq_concurrency` + 2
@@ -23,6 +30,10 @@ Sidekiq.configure_server do |config|
config['pool'] = Sidekiq.options[:concurrency] + 2
ActiveRecord::Base.establish_connection(config)
Rails.logger.debug("Connection Pool size for Sidekiq Server is now: #{ActiveRecord::Base.connection.pool.instance_variable_get('@size')}")
+
+ # Avoid autoload issue such as 'Mail::Parsers::AddressStruct'
+ # https://github.com/mikel/mail/issues/912#issuecomment-214850355
+ Mail.eager_autoload!
end
Sidekiq.configure_client do |config|
diff --git a/config/initializers/smtp_settings.rb.sample b/config/initializers/smtp_settings.rb.sample
index 2287a76fca7..bd37080b1c8 100644
--- a/config/initializers/smtp_settings.rb.sample
+++ b/config/initializers/smtp_settings.rb.sample
@@ -10,6 +10,7 @@
if Rails.env.production?
Rails.application.config.action_mailer.delivery_method = :smtp
+ ActionMailer::Base.delivery_method = :smtp
ActionMailer::Base.smtp_settings = {
address: "email.server.com",
port: 465,
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 de6094fa0ed..2a9fe30b0e6 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -89,10 +89,10 @@ Rails.application.routes.draw do
mount Grack::AuthSpawner, at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\/(info\/lfs|gitlab-lfs)/.match(request.path_info) }, via: [:get, :post, :put]
# Help
- get 'help' => 'help#index'
- get 'help/:category/:file' => 'help#show', as: :help_page, constraints: { category: /.*/, file: /[^\/\.]+/ }
- get 'help/shortcuts'
- get 'help/ui' => 'help#ui'
+ get 'help' => 'help#index'
+ get 'help/shortcuts' => 'help#shortcuts'
+ get 'help/ui' => 'help#ui'
+ get 'help/*path' => 'help#show', as: :help_page
#
# Global snippets
@@ -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
@@ -295,7 +296,7 @@ Rails.application.routes.draw do
post :repository_check
end
- resources :runner_projects
+ resources :runner_projects, only: [:create, :destroy]
end
end
@@ -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
@@ -479,6 +479,7 @@ Rails.application.routes.draw do
get :download_export
get :autocomplete_sources
get :activity
+ get :refs
end
scope module: :projects do
@@ -614,10 +615,18 @@ Rails.application.routes.draw do
post :retry_builds
post :revert
post :cherry_pick
+ get :diff_for_path
end
end
- resources :compare, only: [:index, :create]
+ resources :compare, only: [:index, :create] do
+ collection do
+ get :diff_for_path
+ end
+ end
+
+ get '/compare/:from...:to', to: 'compare#show', as: 'compare', constraints: { from: /.+/, to: /.+/ }
+
resources :network, only: [:show], constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ }
resources :graphs, only: [:show], constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ } do
@@ -628,9 +637,6 @@ Rails.application.routes.draw do
end
end
- get '/compare/:from...:to' => 'compare#show', :as => 'compare',
- :constraints => { from: /.+/, to: /.+/ }
-
resources :snippets, constraints: { id: /\d+/ } do
member do
get 'raw'
@@ -651,7 +657,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
@@ -705,12 +711,14 @@ Rails.application.routes.draw do
post :toggle_subscription
post :toggle_award_emoji
post :remove_wip
+ get :diff_for_path
end
collection do
get :branch_from
get :branch_to
get :update_branches
+ get :diff_for_path
end
end
@@ -719,7 +727,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]
@@ -741,6 +749,7 @@ Rails.application.routes.draw do
get :status
post :cancel
post :retry
+ post :play
post :erase
get :trace
get :raw
diff --git a/db/fixtures/development/14_builds.rb b/db/fixtures/development/14_builds.rb
index 51ff451eb4c..124704cb451 100644
--- a/db/fixtures/development/14_builds.rb
+++ b/db/fixtures/development/14_builds.rb
@@ -1,13 +1,34 @@
class Gitlab::Seeder::Builds
+ STAGES = %w[build notify_build test notify_test deploy notify_deploy]
+
def initialize(project)
@project = project
end
def seed!
- ci_commits.each do |ci_commit|
+ pipelines.each do |pipeline|
begin
- build_create!(ci_commit, name: 'test build 1')
- build_create!(ci_commit, status: 'success', name: 'test build 2')
+ build_create!(pipeline, name: 'build:linux', stage: 'build')
+ build_create!(pipeline, name: 'build:osx', stage: 'build')
+
+ build_create!(pipeline, name: 'slack post build', stage: 'notify_build')
+
+ build_create!(pipeline, name: 'rspec:linux', stage: 'test')
+ build_create!(pipeline, name: 'rspec:windows', stage: 'test')
+ build_create!(pipeline, name: 'rspec:windows', stage: 'test')
+ build_create!(pipeline, name: 'rspec:osx', stage: 'test')
+ build_create!(pipeline, name: 'spinach:linux', stage: 'test')
+ build_create!(pipeline, name: 'spinach:osx', stage: 'test')
+ build_create!(pipeline, name: 'cucumber:linux', stage: 'test')
+ build_create!(pipeline, name: 'cucumber:osx', stage: 'test')
+
+ build_create!(pipeline, name: 'slack post test', stage: 'notify_test')
+
+ build_create!(pipeline, name: 'staging', stage: 'deploy', environment: 'staging')
+ build_create!(pipeline, name: 'production', stage: 'deploy', environment: 'production', when: 'manual')
+
+ commit_status_create!(pipeline, name: 'jenkins')
+
print '.'
rescue ActiveRecord::RecordInvalid
print 'F'
@@ -15,8 +36,8 @@ class Gitlab::Seeder::Builds
end
end
- def ci_commits
- commits = @project.repository.commits('master', nil, 5)
+ def pipelines
+ commits = @project.repository.commits('master', limit: 5)
commits_sha = commits.map { |commit| commit.raw.id }
commits_sha.map do |sha|
@project.ensure_pipeline(sha, 'master')
@@ -25,11 +46,11 @@ class Gitlab::Seeder::Builds
[]
end
- def build_create!(ci_commit, opts = {})
- attributes = build_attributes_for(ci_commit).merge(opts)
+ def build_create!(pipeline, opts = {})
+ attributes = build_attributes_for(pipeline, opts)
build = Ci::Build.new(attributes)
- if %w(success failed).include?(build.status)
+ if opts[:name].start_with?('build')
artifacts_cache_file(artifacts_archive_path) do |file|
build.artifacts_file = file
end
@@ -40,19 +61,28 @@ class Gitlab::Seeder::Builds
end
build.save!
+ build.update(status: build_status)
if %w(running success failed).include?(build.status)
# We need to set build trace after saving a build (id required)
build.trace = FFaker::Lorem.paragraphs(6).join("\n\n")
end
end
+
+ def commit_status_create!(pipeline, opts = {})
+ attributes = commit_status_attributes_for(pipeline, opts)
+ GenericCommitStatus.create(attributes)
+ end
+
+ def commit_status_attributes_for(pipeline, opts)
+ { name: 'test build', stage: 'test', stage_idx: stage_index(opts[:stage]),
+ ref: 'master', user: build_user, project: @project, pipeline: pipeline,
+ created_at: Time.now, updated_at: Time.now
+ }.merge(opts)
+ end
- def build_attributes_for(ci_commit)
- { name: 'test build', commands: "$ build command",
- stage: 'test', stage_idx: 1, ref: 'master',
- user_id: build_user, gl_project_id: @project.id,
- status: build_status, commit_id: ci_commit.id,
- created_at: Time.now, updated_at: Time.now }
+ def build_attributes_for(pipeline, opts)
+ commit_status_attributes_for(pipeline, opts).merge(commands: '$ build command')
end
def build_user
@@ -63,13 +93,16 @@ class Gitlab::Seeder::Builds
Ci::Build::AVAILABLE_STATUSES.sample
end
+ def stage_index(stage)
+ STAGES.index(stage) || 0
+ end
+
def artifacts_archive_path
Rails.root + 'spec/fixtures/ci_build_artifacts.zip'
end
def artifacts_metadata_path
Rails.root + 'spec/fixtures/ci_build_artifacts_metadata.gz'
-
end
def artifacts_cache_file(file_path)
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/20160509091049_add_locked_to_ci_runner.rb b/db/migrate/20160509091049_add_locked_to_ci_runner.rb
new file mode 100644
index 00000000000..3fbaef3b7f0
--- /dev/null
+++ b/db/migrate/20160509091049_add_locked_to_ci_runner.rb
@@ -0,0 +1,13 @@
+class AddLockedToCiRunner < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default(:ci_runners, :locked, :boolean,
+ default: false, allow_null: false)
+ end
+
+ def down
+ remove_column(:ci_runners, :locked)
+ 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/20160519203051_add_developers_can_merge_to_protected_branches.rb b/db/migrate/20160519203051_add_developers_can_merge_to_protected_branches.rb
new file mode 100644
index 00000000000..15ad8e8bcbb
--- /dev/null
+++ b/db/migrate/20160519203051_add_developers_can_merge_to_protected_branches.rb
@@ -0,0 +1,9 @@
+class AddDevelopersCanMergeToProtectedBranches < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ disable_ddl_transaction!
+
+ def change
+ add_column_with_default :protected_branches, :developers_can_merge, :boolean, default: false, allow_null: false
+ 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/20160615191922_set_missing_stage_on_ci_builds.rb b/db/migrate/20160615191922_set_missing_stage_on_ci_builds.rb
new file mode 100644
index 00000000000..bd0463886bc
--- /dev/null
+++ b/db/migrate/20160615191922_set_missing_stage_on_ci_builds.rb
@@ -0,0 +1,9 @@
+class SetMissingStageOnCiBuilds < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ def up
+ update_column_in_batches(:ci_builds, :stage, :test) do |table, query|
+ query.where(table[:stage].eq(nil))
+ end
+ end
+end
diff --git a/db/migrate/20160616102642_remove_duplicated_keys.rb b/db/migrate/20160616102642_remove_duplicated_keys.rb
new file mode 100644
index 00000000000..180a75e0998
--- /dev/null
+++ b/db/migrate/20160616102642_remove_duplicated_keys.rb
@@ -0,0 +1,19 @@
+# rubocop:disable all
+class RemoveDuplicatedKeys < ActiveRecord::Migration
+ def up
+ 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 #{quote_table_name(:keys)}
+ WHERE fingerprint = #{fingerprint}
+ AND id != (
+ SELECT id FROM (
+ SELECT max(id) AS id
+ FROM #{quote_table_name(:keys)}
+ WHERE fingerprint = #{fingerprint}
+ ) max_ids
+ )
+ })
+ end
+ end
+end
diff --git a/db/migrate/20160616103005_remove_keys_fingerprint_index_if_exists.rb b/db/migrate/20160616103005_remove_keys_fingerprint_index_if_exists.rb
new file mode 100644
index 00000000000..4bb4204cebd
--- /dev/null
+++ b/db/migrate/20160616103005_remove_keys_fingerprint_index_if_exists.rb
@@ -0,0 +1,21 @@
+class RemoveKeysFingerprintIndexIfExists < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ disable_ddl_transaction!
+
+ # https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/250
+ # That MR was added on gitlab-ee so we need to check if the index
+ # already exists because we want to do is create an unique index instead.
+
+ def up
+ if index_exists?(:keys, :fingerprint)
+ remove_index :keys, :fingerprint
+ end
+ end
+
+ def down
+ unless index_exists?(:keys, :fingerprint)
+ add_concurrent_index :keys, :fingerprint
+ end
+ end
+end
diff --git a/db/migrate/20160616103948_add_unique_index_to_keys_fingerprint.rb b/db/migrate/20160616103948_add_unique_index_to_keys_fingerprint.rb
new file mode 100644
index 00000000000..e35af38aac3
--- /dev/null
+++ b/db/migrate/20160616103948_add_unique_index_to_keys_fingerprint.rb
@@ -0,0 +1,13 @@
+class AddUniqueIndexToKeysFingerprint < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :keys, :fingerprint, unique: true
+ end
+
+ def down
+ remove_index :keys, :fingerprint
+ end
+end
diff --git a/db/migrate/20160620115026_add_index_on_runners_locked.rb b/db/migrate/20160620115026_add_index_on_runners_locked.rb
new file mode 100644
index 00000000000..dfa5110dea4
--- /dev/null
+++ b/db/migrate/20160620115026_add_index_on_runners_locked.rb
@@ -0,0 +1,12 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddIndexOnRunnersLocked < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ disable_ddl_transaction!
+
+ def change
+ add_concurrent_index :ci_runners, :locked
+ 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/20160629025435_add_column_in_progress_merge_commit_sha_to_merge_requests.rb b/db/migrate/20160629025435_add_column_in_progress_merge_commit_sha_to_merge_requests.rb
new file mode 100644
index 00000000000..7c5f76572ef
--- /dev/null
+++ b/db/migrate/20160629025435_add_column_in_progress_merge_commit_sha_to_merge_requests.rb
@@ -0,0 +1,8 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddColumnInProgressMergeCommitShaToMergeRequests < ActiveRecord::Migration
+ def change
+ add_column :merge_requests, :in_progress_merge_commit_sha, :string
+ 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/migrate/20160712171823_remove_award_emojis_with_no_user.rb b/db/migrate/20160712171823_remove_award_emojis_with_no_user.rb
new file mode 100644
index 00000000000..668c22bb51c
--- /dev/null
+++ b/db/migrate/20160712171823_remove_award_emojis_with_no_user.rb
@@ -0,0 +1,21 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class RemoveAwardEmojisWithNoUser < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # When using the methods "add_concurrent_index" or "add_column_with_default"
+ # you must disable the use of transactions as these methods can not run in an
+ # existing transaction. When using "add_concurrent_index" make sure that this
+ # method is the _only_ method called in the migration, any other changes
+ # should go in a separate migration. This ensures that upon failure _only_ the
+ # index creation fails and can be retried or reverted easily.
+ #
+ # To disable transactions uncomment the following line and remove these
+ # comments:
+ # disable_ddl_transaction!
+
+ def up
+ AwardEmoji.joins('LEFT JOIN users ON users.id = user_id').where('users.id IS NULL').destroy_all
+ end
+end
diff --git a/db/migrate/20160715132507_add_user_id_to_pipeline.rb b/db/migrate/20160715132507_add_user_id_to_pipeline.rb
new file mode 100644
index 00000000000..af0461c4daf
--- /dev/null
+++ b/db/migrate/20160715132507_add_user_id_to_pipeline.rb
@@ -0,0 +1,7 @@
+class AddUserIdToPipeline < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ def change
+ add_column :ci_commits, :user_id, :integer
+ end
+end
diff --git a/db/migrate/20160715134306_add_index_for_pipeline_user_id.rb b/db/migrate/20160715134306_add_index_for_pipeline_user_id.rb
new file mode 100644
index 00000000000..7c991c6d998
--- /dev/null
+++ b/db/migrate/20160715134306_add_index_for_pipeline_user_id.rb
@@ -0,0 +1,9 @@
+class AddIndexForPipelineUserId < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ disable_ddl_transaction!
+
+ def change
+ add_concurrent_index :ci_commits, :user_id
+ end
+end
diff --git a/db/migrate/20160716115710_add_when_and_yaml_variables_to_ci_builds.rb b/db/migrate/20160716115710_add_when_and_yaml_variables_to_ci_builds.rb
new file mode 100644
index 00000000000..3e084023a65
--- /dev/null
+++ b/db/migrate/20160716115710_add_when_and_yaml_variables_to_ci_builds.rb
@@ -0,0 +1,8 @@
+class AddWhenAndYamlVariablesToCiBuilds < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ def change
+ add_column :ci_builds, :when, :string
+ add_column :ci_builds, :yaml_variables, :text
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 5a27e9d5cdc..8882377f9f4 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: 20160616084004) do
+ActiveRecord::Schema.define(version: 20160716115710) 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: 20160616084004) 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: 20160616084004) 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,9 @@ ActiveRecord::Schema.define(version: 20160616084004) do
t.datetime "erased_at"
t.string "environment"
t.datetime "artifacts_expire_at"
+ t.integer "artifacts_size"
+ t.string "when"
+ t.text "yaml_variables"
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
@@ -194,6 +201,7 @@ ActiveRecord::Schema.define(version: 20160616084004) do
t.datetime "started_at"
t.datetime "finished_at"
t.integer "duration"
+ t.integer "user_id"
end
add_index "ci_commits", ["gl_project_id", "sha"], name: "index_ci_commits_on_gl_project_id_and_sha", using: :btree
@@ -205,6 +213,7 @@ ActiveRecord::Schema.define(version: 20160616084004) do
add_index "ci_commits", ["project_id"], name: "index_ci_commits_on_project_id", using: :btree
add_index "ci_commits", ["sha"], name: "index_ci_commits_on_sha", using: :btree
add_index "ci_commits", ["status"], name: "index_ci_commits_on_status", using: :btree
+ add_index "ci_commits", ["user_id"], name: "index_ci_commits_on_user_id", using: :btree
create_table "ci_events", force: :cascade do |t|
t.integer "project_id"
@@ -287,9 +296,11 @@ ActiveRecord::Schema.define(version: 20160616084004) do
t.string "platform"
t.string "architecture"
t.boolean "run_untagged", default: true, null: false
+ t.boolean "locked", default: false, null: false
end
add_index "ci_runners", ["description"], name: "index_ci_runners_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
+ add_index "ci_runners", ["locked"], name: "index_ci_runners_on_locked", using: :btree
add_index "ci_runners", ["token"], name: "index_ci_runners_on_token", using: :btree
add_index "ci_runners", ["token"], name: "index_ci_runners_on_token_trigram", using: :gin, opclasses: {"token"=>"gin_trgm_ops"}
@@ -507,6 +518,7 @@ ActiveRecord::Schema.define(version: 20160616084004) do
end
add_index "keys", ["created_at", "id"], name: "index_keys_on_created_at_and_id", using: :btree
+ add_index "keys", ["fingerprint"], name: "index_keys_on_fingerprint", unique: true, using: :btree
add_index "keys", ["user_id"], name: "index_keys_on_user_id", using: :btree
create_table "label_links", force: :cascade do |t|
@@ -586,6 +598,8 @@ ActiveRecord::Schema.define(version: 20160616084004) 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
@@ -614,8 +628,8 @@ ActiveRecord::Schema.define(version: 20160616084004) do
t.integer "merge_user_id"
t.string "merge_commit_sha"
t.datetime "deleted_at"
+ t.string "in_progress_merge_commit_sha"
end
-
add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
add_index "merge_requests", ["author_id"], name: "index_merge_requests_on_author_id", using: :btree
add_index "merge_requests", ["created_at", "id"], name: "index_merge_requests_on_created_at_and_id", using: :btree
@@ -682,10 +696,12 @@ ActiveRecord::Schema.define(version: 20160616084004) 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
@@ -707,6 +723,7 @@ ActiveRecord::Schema.define(version: 20160616084004) do
t.integer "level", default: 0, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
+ t.text "events"
end
add_index "notification_settings", ["source_id", "source_type"], name: "index_notification_settings_on_source_id_and_source_type", using: :btree
@@ -792,38 +809,39 @@ ActiveRecord::Schema.define(version: 20160616084004) 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
@@ -844,11 +862,12 @@ ActiveRecord::Schema.define(version: 20160616084004) do
add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree
create_table "protected_branches", force: :cascade do |t|
- t.integer "project_id", null: false
- t.string "name", null: false
+ t.integer "project_id", null: false
+ t.string "name", null: false
t.datetime "created_at"
t.datetime "updated_at"
- t.boolean "developers_can_push", default: false, null: false
+ t.boolean "developers_can_push", default: false, null: false
+ t.boolean "developers_can_merge", default: false, null: false
end
add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree
@@ -872,6 +891,8 @@ ActiveRecord::Schema.define(version: 20160616084004) 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 5d89d0c9821..cc0b6e0c1e5 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -3,36 +3,39 @@
## User documentation
- [API](api/README.md) Automate GitLab via a simple and powerful API.
-- [CI](ci/README.md) GitLab Continuous Integration (CI) getting started, `.gitlab-ci.yml` options, and examples.
+- [CI/CD](ci/README.md) GitLab Continuous Integration (CI) and Continuous Delivery (CD) getting started, `.gitlab-ci.yml` options, and examples.
- [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab.
+- [Container Registry](container_registry/README.md) Learn how to use GitLab Container Registry.
- [GitLab Basics](gitlab-basics/README.md) Find step by step how to start working on your commandline and on GitLab.
- [Importing to GitLab](workflow/importing/README.md).
+- [Importing and exporting projects between instances](user/project/settings/import_export.md).
- [Markdown](markdown/markdown.md) GitLab's advanced formatting system.
-- [Migrating from SVN](workflow/importing/migrating_from_svn.md) Convert a SVN repository to Git and GitLab
-- [Permissions](permissions/permissions.md) Learn what each role in a project (external/guest/reporter/developer/master/owner) can do.
+- [Migrating from SVN](workflow/importing/migrating_from_svn.md) Convert a SVN repository to Git and GitLab.
+- [Permissions](user/permissions.md) Learn what each role in a project (external/guest/reporter/developer/master/owner) can do.
- [Profile Settings](profile/README.md)
- [Project Services](project_services/project_services.md) Integrate a project with external services, such as CI and chat.
- [Public access](public_access/public_access.md) Learn how you can allow public and internal access to projects.
-- [Container Registry](container_registry/README.md) Learn how to use GitLab Container Registry.
- [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects.
- [Webhooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project.
- [Workflow](workflow/README.md) Using GitLab functionality and importing projects from GitHub and SVN.
## 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
+- [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.
- [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages.
- [Libravatar](customization/libravatar.md) Use Libravatar for user avatars.
- [Log system](administration/logs.md) Log system.
- [Environment Variables](administration/environment_variables.md) to configure GitLab.
-- [Operations](operations/README.md) Keeping GitLab up and running
+- [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 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.
@@ -41,11 +44,12 @@
- [Migrate GitLab CI to CE/EE](migrate_ci_to_ce/README.md) Follow this guide to migrate your existing GitLab CI data to GitLab CE/EE.
- [Git LFS configuration](workflow/lfs/lfs_administration.md)
- [Housekeeping](administration/housekeeping.md) Keep your Git repository tidy and fast.
-- [GitLab Performance Monitoring](monitoring/performance/introduction.md) Configure GitLab and InfluxDB for measuring performance metrics
-- [Monitoring uptime](monitoring/health_check.md) Check the server status using the health check endpoint
-- [Sidekiq Troubleshooting](administration/troubleshooting/sidekiq.md) Debug when Sidekiq appears hung and is not processing jobs
-- [High Availability](administration/high_availability/README.md) Configure multiple servers for scaling or high availability
-- [Container Registry](administration/container_registry.md) Configure Docker Registry with GitLab
+- [GitLab Performance Monitoring](monitoring/performance/introduction.md) Configure GitLab and InfluxDB for measuring performance metrics.
+- [Monitoring uptime](monitoring/health_check.md) Check the server status using the health check endpoint.
+- [Debugging Tips](administration/troubleshooting/debug.md) Tips to debug problems when things go wrong
+- [Sidekiq Troubleshooting](administration/troubleshooting/sidekiq.md) Debug when Sidekiq appears hung and is not processing jobs.
+- [High Availability](administration/high_availability/README.md) Configure multiple servers for scaling or high availability.
+- [Container Registry](administration/container_registry.md) Configure Docker Registry with GitLab.
## Contributor documentation
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/container_registry.md b/doc/administration/container_registry.md
index 7870669fa77..d5d43303454 100644
--- a/doc/administration/container_registry.md
+++ b/doc/administration/container_registry.md
@@ -22,6 +22,7 @@ You can read more about Docker Registry at https://docs.docker.com/registry/intr
- [Disable Container Registry per project](#disable-container-registry-per-project)
- [Disable Container Registry for new projects site-wide](#disable-container-registry-for-new-projects-site-wide)
- [Container Registry storage path](#container-registry-storage-path)
+- [Container Registry storage driver](#container-registry-storage-driver)
- [Storage limitations](#storage-limitations)
- [Changelog](#changelog)
@@ -84,6 +85,17 @@ GitLab does not ship with a Registry init file. Hence, [restarting GitLab][resta
will not restart the Registry should you modify its settings. Read the upstream
documentation on how to achieve that.
+The Docker Registry configuration will need `container_registry` as the service and `https://gitlab.example.com/jwt/auth` as the realm:
+
+```
+auth:
+ token:
+ realm: https://gitlab.example.com/jwt/auth
+ service: container_registry
+ issuer: gitlab-issuer
+ rootcertbundle: /root/certs/certbundle
+```
+
## Container Registry domain configuration
There are two ways you can configure the Registry's external domain.
@@ -306,8 +318,12 @@ the Container Registry by themselves, follow the steps below.
## Container Registry storage path
-To change the storage path where Docker images will be stored, follow the
-steps below.
+>**Note:**
+For configuring storage in the cloud instead of the filesystem, see the
+[storage driver configuration](#container-registry-storage-driver).
+
+If you want to store your images on the filesystem, you can change the storage
+path for the Container Registry, follow the steps below.
This path is accessible to:
@@ -349,6 +365,72 @@ The default location where images are stored in source installations, is
1. Save the file and [restart GitLab][] for the changes to take effect.
+## Container Registry storage driver
+
+You can configure the Container Registry to use a different storage backend by
+configuring a different storage driver. By default the GitLab Container Registry
+is configured to use the filesystem driver, which makes use of [storage path](#container-registry-storage-path)
+configuration.
+
+The different supported drivers are:
+
+| Driver | Description |
+|------------|-------------------------------------|
+| filesystem | Uses a path on the local filesystem |
+| azure | Microsoft Azure Blob Storage |
+| gcs | Google Cloud Storage |
+| s3 | Amazon Simple Storage Service |
+| swift | OpenStack Swift Object Storage |
+| oss | Aliyun OSS |
+
+Read more about the individual driver's config options in the
+[Docker Registry docs][storage-config].
+
+> **Warning** GitLab will not backup Docker images that are not stored on the
+filesystem. Remember to enable backups with your object storage provider if
+desired.
+
+---
+
+**Omnibus GitLab installations**
+
+To configure the storage driver in Omnibus:
+
+1. Edit `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ registry['storage'] = {
+ 's3' => {
+ 'accesskey' => 's3-access-key',
+ 'secretkey' => 's3-secret-key-for-access-key',
+ 'bucket' => 'your-s3-bucket'
+ }
+ }
+ ```
+
+1. Save the file and [reconfigure GitLab][] for the changes to take effect.
+
+---
+
+**Installations from source**
+
+Configuring the storage driver is done in your registry config YML file created
+when you [deployed your docker registry][registry-deploy].
+
+Example:
+
+```
+storage:
+ s3:
+ accesskey: 'AKIAKIAKI'
+ secretkey: 'secret123'
+ bucket: 'gitlab-registry-bucket-AKIAKIAKI'
+ cache:
+ blobdescriptor: inmemory
+ delete:
+ enabled: true
+```
+
## Storage limitations
Currently, there is no storage limitation, which means a user can upload an
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
new file mode 100644
index 00000000000..66fd9491e85
--- /dev/null
+++ b/doc/administration/img/access_restrictions.png
Binary files differ
diff --git a/doc/administration/img/custom_hooks_error_msg.png b/doc/administration/img/custom_hooks_error_msg.png
new file mode 100644
index 00000000000..92e87e15fb3
--- /dev/null
+++ b/doc/administration/img/custom_hooks_error_msg.png
Binary files differ
diff --git a/doc/administration/img/housekeeping_settings.png b/doc/administration/img/housekeeping_settings.png
index f7c5bc44367..f72ad9a45d5 100644
--- a/doc/administration/img/housekeeping_settings.png
+++ b/doc/administration/img/housekeeping_settings.png
Binary files differ
diff --git a/doc/administration/img/restricted_url.png b/doc/administration/img/restricted_url.png
new file mode 100644
index 00000000000..0a677433dcf
--- /dev/null
+++ b/doc/administration/img/restricted_url.png
Binary files differ
diff --git a/doc/administration/raketasks/project_import_export.md b/doc/administration/raketasks/project_import_export.md
new file mode 100644
index 00000000000..c212059b9d5
--- /dev/null
+++ b/doc/administration/raketasks/project_import_export.md
@@ -0,0 +1,33 @@
+# Project import/export
+
+>**Note:**
+ - This feature was [introduced][ce-3050] in GitLab 8.9
+ - Importing will not be possible if the import instance version is lower
+ than that of the exporter.
+ - For existing installations, the project import option has to be enabled in
+ application settings (`/admin/application_settings`) under 'Import sources'.
+ - The exports are stored in a temporary [shared directory][tmp] and are deleted
+ every 24 hours by a specific worker.
+
+The GitLab Import/Export version can be checked by using:
+
+```bash
+# Omnibus installations
+sudo gitlab-rake gitlab:import_export:version
+
+# Installations from source
+bundle exec rake gitlab:import_export:version RAILS_ENV=production
+```
+
+The current list of DB tables that will get exported can be listed by using:
+
+```bash
+# Omnibus installations
+sudo gitlab-rake gitlab:import_export:data
+
+# Installations from source
+bundle exec rake gitlab:import_export:data RAILS_ENV=production
+```
+
+[ce-3050]: https://gitlab.com/gitlab-org/gitlab-ce/issues/3050
+[tmp]: ../../development/shared_files.md
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
new file mode 100644
index 00000000000..d127d7b85e5
--- /dev/null
+++ b/doc/administration/troubleshooting/debug.md
@@ -0,0 +1,169 @@
+# Debugging Tips
+
+Sometimes things don't work the way they should. Here are some tips on debugging issues out
+in production.
+
+## Mail not working
+
+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
+```
+
+On CentOS:
+
+```
+sudo yum install gdb
+```
+
+## Common Problems
+
+Many of the tips to diagnose issues below apply to many different situations. We'll use one
+concrete example to illustrate what you can do to learn what is going wrong.
+
+### 502 Gateway Timeout after unicorn spins at 100% CPU
+
+This error occurs when the Web server times out (default: 60 s) after not
+hearing back from the unicorn worker. If the CPU spins to 100% while this in
+progress, there may be something taking longer than it should.
+
+To fix this issue, we first need to figure out what is happening. The
+following tips are only recommended if you do NOT mind users being affected by
+downtime. Otherwise skip to the next section.
+
+1. Load the problematic URL
+1. Run `sudo gdb -p <PID>` to attach to the unicorn process.
+1. In the gdb window, type:
+
+ ```
+ call (void) rb_backtrace()
+ ```
+
+1. This forces the process to generate a Ruby backtrace. Check
+ `/var/log/gitlab/unicorn/unicorn_stderr.log` for the backtace. For example, you may see:
+
+ ```ruby
+ from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:33:in `block in start'
+ from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:33:in `loop'
+ from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:36:in `block (2 levels) in start'
+ from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:44:in `sample'
+ from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:68:in `sample_objects'
+ from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:68:in `each_with_object'
+ from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:68:in `each'
+ from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:69:in `block in sample_objects'
+ from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:69:in `name'
+ ```
+
+1. To see the current threads, run:
+
+ ```
+ apply all thread bt
+ ```
+
+1. Once you're done debugging with `gdb`, be sure to detach from the process and exit:
+
+ ```
+ detach
+ exit
+ ```
+
+Note that if the unicorn process terminates before you are able to run these
+commands, gdb will report an error. To buy more time, you can always raise the
+Unicorn timeout. For omnibus users, you can edit `/etc/gitlab/gitlab.rb` and
+increase it from 60 seconds to 300:
+
+```ruby
+unicorn['worker_timeout'] = 300
+```
+
+For source installations, edit `config/unicorn.rb`.
+
+[Reconfigure] GitLab for the changes to take effect.
+
+[Reconfigure]: ../restart_gitlab.md#omnibus-gitlab-reconfigure
+
+#### Troubleshooting without affecting other users
+
+The previous section attached to a running unicorn process, and this may have
+undesirable effects for users trying to access GitLab during this time. If you
+are concerned about affecting others during a production system, you can run a
+separate Rails process to debug the issue:
+
+1. Log in to your GitLab account.
+1. Copy the URL that is causing problems (e.g. https://gitlab.com/ABC).
+1. Obtain the private token for your user (Profile Settings -> Account).
+1. Bring up the GitLab Rails console. For omnibus users, run:
+
+ ````
+ sudo gitlab-rails console
+ ```
+
+1. At the Rails console, run:
+
+ ```ruby
+ [1] pry(main)> app.get '<URL FROM STEP 1>/private_token?<TOKEN FROM STEP 2>'
+ ```
+
+ For example:
+
+ ```ruby
+ [1] pry(main)> app.get 'https://gitlab.com/gitlab-org/gitlab-ce/issues/1?private_token=123456'
+ ```
+
+1. In a new window, run `top`. It should show this ruby process using 100% CPU. Write down the PID.
+1. Follow step 2 from the previous section on using gdb.
+
+# More information
+
+* [Debugging Stuck Ruby Processes](https://blog.newrelic.com/2013/04/29/debugging-stuck-ruby-processes-what-to-do-before-you-kill-9/)
+* [Cheatsheet of using gdb and ruby processes](gdb-stuck-ruby.txt)
diff --git a/doc/administration/troubleshooting/gdb-stuck-ruby.txt b/doc/administration/troubleshooting/gdb-stuck-ruby.txt
new file mode 100644
index 00000000000..13d5dfcffa4
--- /dev/null
+++ b/doc/administration/troubleshooting/gdb-stuck-ruby.txt
@@ -0,0 +1,142 @@
+# Here's the script I'll use to demonstrate - it just loops forever:
+
+$ cat test.rb
+#!/usr/bin/env ruby
+
+loop do
+ sleep 1
+end
+
+# Now, I'll start the script in the background, and redirect stdout and stderr
+# to /dev/null:
+
+$ ruby ./test.rb >/dev/null 2>/dev/null &
+[1] 1343
+
+# Next, I'll grab the PID of the script (1343):
+
+$ ps aux | grep test.rb
+vagrant 1343 0.0 0.4 3884 1652 pts/0 S 14:42 0:00 ruby ./test.rb
+vagrant 1345 0.0 0.2 4624 852 pts/0 S+ 14:42 0:00 grep --color=auto test.rb
+
+# Now I start gdb. Note that I'm using sudo here. This may or may not be
+# necessary in your setup. I'd try without sudo first, and fall back to adding
+# it if the next step fails:
+
+$ sudo gdb
+GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2.1) 7.4-2012.04
+Copyright (C) 2012 Free Software Foundation, Inc.
+License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
+This is free software: you are free to change and redistribute it.
+There is NO WARRANTY, to the extent permitted by law. Type "show copying"
+and "show warranty" for details.
+This GDB was configured as "i686-linux-gnu".
+For bug reporting instructions, please see:
+<http://bugs.launchpad.net/gdb-linaro/>.
+
+# OK, now I'm in gdb, and I want to instruct it to attach to our Ruby process.
+# I can do that using the 'attach' command, which takes a PID (the one we
+# gathered above):
+
+(gdb) attach 1343
+Attaching to process 1343
+Reading symbols from /opt/vagrant_ruby/bin/ruby...done.
+Reading symbols from /lib/i386-linux-gnu/librt.so.1...(no debugging symbols found)...done.
+Loaded symbols for /lib/i386-linux-gnu/librt.so.1
+Reading symbols from /lib/i386-linux-gnu/libdl.so.2...(no debugging symbols found)...done.
+Loaded symbols for /lib/i386-linux-gnu/libdl.so.2
+Reading symbols from /lib/i386-linux-gnu/libcrypt.so.1...(no debugging symbols found)...done.
+Loaded symbols for /lib/i386-linux-gnu/libcrypt.so.1
+Reading symbols from /lib/i386-linux-gnu/libm.so.6...(no debugging symbols found)...done.
+Loaded symbols for /lib/i386-linux-gnu/libm.so.6
+Reading symbols from /lib/i386-linux-gnu/libc.so.6...(no debugging symbols found)...done.
+Loaded symbols for /lib/i386-linux-gnu/libc.so.6
+Reading symbols from /lib/i386-linux-gnu/libpthread.so.0...(no debugging symbols found)...done.
+[Thread debugging using libthread_db enabled]
+Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1".
+Loaded symbols for /lib/i386-linux-gnu/libpthread.so.0
+Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.
+Loaded symbols for /lib/ld-linux.so.2
+0xb770c424 in __kernel_vsyscall ()
+
+# Great, now gdb is attached to the target process. If the step above fails, try
+# going back and running gdb under sudo. The next thing I want to do is gather
+# C-level backtraces from all threads in the process. The following command
+# stands for 'thread apply all backtrace':
+
+(gdb) t a a bt
+
+Thread 1 (Thread 0xb74d76c0 (LWP 1343)):
+#0 0xb770c424 in __kernel_vsyscall ()
+#1 0xb75d7abd in select () from /lib/i386-linux-gnu/libc.so.6
+#2 0x08069c56 in rb_thread_wait_for (time=...) at eval.c:11376
+#3 0x080a20fd in rb_f_sleep (argc=1, argv=0xbf85f490) at process.c:1633
+#4 0x0805e0e2 in call_cfunc (argv=0xbf85f490, argc=1, len=-1, recv=3075299660, func=0x80a20b0 <rb_f_sleep>)
+ at eval.c:5778
+#5 rb_call0 (klass=3075304600, recv=3075299660, id=9393, oid=9393, argc=1, argv=0xbf85f490, body=0xb74c85a8, flags=2)
+ at eval.c:5928
+#6 0x0805e35d in rb_call (klass=3075304600, recv=3075299660, mid=9393, argc=1, argv=0xbf85f490, scope=1,
+ self=<optimized out>) at eval.c:6176
+#7 0x080651ec in rb_eval (self=3075299660, n=0xb74c4e1c) at eval.c:3521
+#8 0x0805c31c in rb_yield_0 (val=6, self=3075299660, klass=<optimized out>, flags=0, avalue=0) at eval.c:5095
+#9 0x0806a1e5 in loop_i () at eval.c:5227
+#10 0x08058dbd in rb_rescue2 (b_proc=0x806a1c0 <loop_i>, data1=0, r_proc=0, data2=0) at eval.c:5491
+#11 0x08058f28 in rb_f_loop () at eval.c:5252
+#12 0x0805e0c1 in call_cfunc (argv=0x0, argc=0, len=0, recv=3075299660, func=0x8058ef0 <rb_f_loop>) at eval.c:5781
+#13 rb_call0 (klass=3075304600, recv=3075299660, id=4121, oid=4121, argc=0, argv=0x0, body=0xb74d4dbc, flags=2)
+ at eval.c:5928
+#14 0x0805e35d in rb_call (klass=3075304600, recv=3075299660, mid=4121, argc=0, argv=0x0, scope=1, self=<optimized out>)
+ at eval.c:6176
+#15 0x080651ec in rb_eval (self=3075299660, n=0xb74c4dcc) at eval.c:3521
+#16 0x080662c6 in rb_eval (self=3075299660, n=0xb74c4de0) at eval.c:3236
+#17 0x08068ee4 in ruby_exec_internal () at eval.c:1654
+#18 0x08068f24 in ruby_exec () at eval.c:1674
+#19 0x0806b2cd in ruby_run () at eval.c:1684
+#20 0x08053771 in main (argc=2, argv=0xbf860204, envp=0xbf860210) at main.c:48
+
+# C backtraces are sometimes sufficient, but often Ruby backtraces are necessary
+# for debugging as well. Ruby has a built-in function called rb_backtrace() that
+# we can use to dump out a Ruby backtrace, but it prints to stdout or stderr
+# (depending on your Ruby version), which might have been redirected to a file
+# or to /dev/null (as in our example) when the process started up.
+#
+# To get aroundt this, we'll do a little trick and redirect the target process's
+# stdout and stderr to the current TTY, so that any output from the process
+# will appear directly on our screen.
+
+# First, let's close the existing file descriptors for stdout and stderr
+# (FD 1 and 2, respectively):
+(gdb) call (void) close(1)
+(gdb) call (void) close(2)
+
+# Next, we need to figure out the device name for the current TTY:
+(gdb) shell tty
+/dev/pts/0
+
+# OK, now we can pass the device name obtained above to open() and attach
+# file descriptors 1 and 2 back to the current TTY with these calls:
+
+(gdb) call (int) open("/dev/pts/0", 2, 0)
+$1 = 1
+(gdb) call (int) open("/dev/pts/0", 2, 0)
+$2 = 2
+
+# Finally, we call rb_backtrace() in order to dump the Ruby backtrace:
+
+(gdb) call (void) rb_backtrace()
+ from ./test.rb:4:in `sleep'
+ from ./test.rb:4
+ from ./test.rb:3:in `loop'
+ from ./test.rb:3
+
+# And here's how we get out of gdb. Once you've quit, you'll probably want to
+# clean up the stuck process by killing it.
+
+(gdb) quit
+A debugging session is active.
+
+ Inferior 1 [process 1343] will be detached.
+
+Quit anyway? (y or n) y
+Detaching from program: /opt/vagrant_ruby/bin/ruby, process 1343
+$
diff --git a/doc/api/README.md b/doc/api/README.md
index 73f44603688..d1e6c54c521 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -32,9 +32,11 @@ following locations:
- [Services](services.md)
- [Session](session.md)
- [Settings](settings.md)
+- [Sidekiq metrics](sidekiq_metrics.md)
- [System Hooks](system_hooks.md)
- [Tags](tags.md)
- [Users](users.md)
+- [Todos](todos.md)
### Internal CI API
diff --git a/doc/api/branches.md b/doc/api/branches.md
index abc4732c395..dbe8306c66f 100644
--- a/doc/api/branches.md
+++ b/doc/api/branches.md
@@ -23,6 +23,8 @@ Example response:
{
"name": "master",
"protected": true,
+ "developers_can_push": false,
+ "developers_can_merge": false,
"commit": {
"author_email": "john@example.com",
"author_name": "John Smith",
@@ -64,6 +66,8 @@ Example response:
{
"name": "master",
"protected": true,
+ "developers_can_push": false,
+ "developers_can_merge": false,
"commit": {
"author_email": "john@example.com",
"author_name": "John Smith",
@@ -91,13 +95,15 @@ PUT /projects/:id/repository/branches/:branch/protect
```
```bash
-curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/branches/master/protect
+curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/repository/branches/master/protect?developers_can_push=true&developers_can_merge=true
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a project |
| `branch` | string | yes | The name of the branch |
+| `developers_can_push` | boolean | no | Flag if developers can push to the branch |
+| `developers_can_merge` | boolean | no | Flag if developers can merge to the branch |
Example response:
@@ -117,7 +123,9 @@ Example response:
]
},
"name": "master",
- "protected": true
+ "protected": true,
+ "developers_can_push": true,
+ "developers_can_merge": true
}
```
@@ -158,7 +166,9 @@ Example response:
]
},
"name": "master",
- "protected": false
+ "protected": false,
+ "developers_can_push": false,
+ "developers_can_merge": false
}
```
@@ -196,7 +206,9 @@ Example response:
]
},
"name": "newbranch",
- "protected": false
+ "protected": false,
+ "developers_can_push": false,
+ "developers_can_merge": false
}
```
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 0bc82ef9edb..419fb8f85d8 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -28,7 +28,7 @@ GET /issues?labels=foo,bar&state=opened
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `state` | string | no | Return all issues or just those that are `opened` or `closed`|
-| `labels` | string | no | Comma-separated list of label names |
+| `labels` | string | no | Comma-separated list of label names, issues with any of the labels will be returned |
| `order_by`| string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
| `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` |
@@ -78,7 +78,85 @@ Example response:
"iid" : 6,
"labels" : [],
"subscribed" : false,
- "user_notes_count": 1
+ "user_notes_count": 1,
+ "due_date": "2016-07-22"
+ }
+]
+```
+
+## List group issues
+
+Get a list of a group's issues.
+
+```
+GET /groups/:id/issues
+GET /groups/:id/issues?state=opened
+GET /groups/:id/issues?state=closed
+GET /groups/:id/issues?labels=foo
+GET /groups/:id/issues?labels=foo,bar
+GET /groups/:id/issues?labels=foo,bar&state=opened
+GET /groups/:id/issues?milestone=1.0.0
+GET /groups/:id/issues?milestone=1.0.0&state=opened
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer | yes | The ID of a group |
+| `state` | string | no | Return all issues or just those that are `opened` or `closed`|
+| `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned |
+| `milestone` | string| no | The milestone title |
+| `order_by`| string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
+| `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` |
+
+
+```bash
+curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/4/issues
+```
+
+Example response:
+
+```json
+[
+ {
+ "project_id" : 4,
+ "milestone" : {
+ "due_date" : null,
+ "project_id" : 4,
+ "state" : "closed",
+ "description" : "Rerum est voluptatem provident consequuntur molestias similique ipsum dolor.",
+ "iid" : 3,
+ "id" : 11,
+ "title" : "v3.0",
+ "created_at" : "2016-01-04T15:31:39.788Z",
+ "updated_at" : "2016-01-04T15:31:39.788Z"
+ },
+ "author" : {
+ "state" : "active",
+ "web_url" : "https://gitlab.example.com/u/root",
+ "avatar_url" : null,
+ "username" : "root",
+ "id" : 1,
+ "name" : "Administrator"
+ },
+ "description" : "Omnis vero earum sunt corporis dolor et placeat.",
+ "state" : "closed",
+ "iid" : 1,
+ "assignee" : {
+ "avatar_url" : null,
+ "web_url" : "https://gitlab.example.com/u/lennie",
+ "state" : "active",
+ "username" : "lennie",
+ "id" : 9,
+ "name" : "Dr. Luella Kovacek"
+ },
+ "labels" : [],
+ "id" : 41,
+ "title" : "Ut commodi ullam eos dolores perferendis nihil sunt.",
+ "updated_at" : "2016-01-04T15:31:46.176Z",
+ "created_at" : "2016-01-04T15:31:46.176Z",
+ "subscribed" : false,
+ "user_notes_count": 1,
+ "due_date": null
}
]
```
@@ -104,7 +182,7 @@ GET /projects/:id/issues?iid=42
| `id` | integer | yes | The ID of a project |
| `iid` | integer | no | Return the issue having the given `iid` |
| `state` | string | no | Return all issues or just those that are `opened` or `closed`|
-| `labels` | string | no | Comma-separated list of label names |
+| `labels` | string | no | Comma-separated list of label names, issues with any of the labels will be returned |
| `milestone` | string| no | The milestone title |
| `order_by`| string | no | Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` |
| `sort` | string | no | Return requests sorted in `asc` or `desc` order. Default is `desc` |
@@ -156,7 +234,8 @@ Example response:
"updated_at" : "2016-01-04T15:31:46.176Z",
"created_at" : "2016-01-04T15:31:46.176Z",
"subscribed" : false,
- "user_notes_count": 1
+ "user_notes_count": 1,
+ "due_date": "2016-07-22"
}
]
```
@@ -219,7 +298,8 @@ Example response:
"updated_at" : "2016-01-04T15:31:46.176Z",
"created_at" : "2016-01-04T15:31:46.176Z",
"subscribed": false,
- "user_notes_count": 1
+ "user_notes_count": 1,
+ "due_date": null
}
```
@@ -244,6 +324,7 @@ POST /projects/:id/issues
| `milestone_id` | integer | no | The ID of a milestone to assign issue |
| `labels` | string | no | Comma-separated label names for an issue |
| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` |
+| `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` |
```bash
curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues?title=Issues%20with%20auth&labels=bug
@@ -275,7 +356,8 @@ Example response:
"updated_at" : "2016-01-07T12:44:33.959Z",
"milestone" : null,
"subscribed" : true,
- "user_notes_count": 0
+ "user_notes_count": 0,
+ "due_date": null
}
```
@@ -303,6 +385,7 @@ PUT /projects/:id/issues/:issue_id
| `labels` | string | no | Comma-separated label names for an issue |
| `state_event` | string | no | The state event of an issue. Set `close` to close the issue and `reopen` to reopen it |
| `updated_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` |
+| `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` |
```bash
curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues/85?state_event=close
@@ -334,7 +417,8 @@ Example response:
"assignee" : null,
"milestone" : null,
"subscribed" : true,
- "user_notes_count": 0
+ "user_notes_count": 0,
+ "due_date": "2016-07-22"
}
```
@@ -411,7 +495,8 @@ Example response:
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/7a190fecbaa68212a4b68aeb6e3acd10?s=80&d=identicon",
"web_url": "https://gitlab.example.com/u/solon.cremin"
- }
+ },
+ "due_date": null
}
```
@@ -465,7 +550,8 @@ Example response:
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/7a190fecbaa68212a4b68aeb6e3acd10?s=80&d=identicon",
"web_url": "https://gitlab.example.com/u/solon.cremin"
- }
+ },
+ "due_date": null
}
```
@@ -518,9 +604,101 @@ 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,
+ "due_date": null
+}
+```
+
+## 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
},
- "subscribed": false
+ "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"
}
```
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 2930f615fc1..a8c3b068d22 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -49,10 +49,10 @@ Parameters:
"state": "active",
"created_at": "2012-04-29T08:46:00Z"
},
- "source_project_id": "2",
- "target_project_id": "3",
+ "source_project_id": 2,
+ "target_project_id": 3,
"labels": [ ],
- "description":"fixed login page css paddings",
+ "description": "fixed login page css paddings",
"work_in_progress": false,
"milestone": {
"id": 5,
@@ -68,7 +68,9 @@ Parameters:
"merge_when_build_succeeds": true,
"merge_status": "can_be_merged",
"subscribed" : false,
- "user_notes_count": 1
+ "user_notes_count": 1,
+ "should_remove_source_branch": true,
+ "force_remove_source_branch": false
}
]
```
@@ -113,10 +115,10 @@ Parameters:
"state": "active",
"created_at": "2012-04-29T08:46:00Z"
},
- "source_project_id": "2",
- "target_project_id": "3",
+ "source_project_id": 2,
+ "target_project_id": 3,
"labels": [ ],
- "description":"fixed login page css paddings",
+ "description": "fixed login page css paddings",
"work_in_progress": false,
"milestone": {
"id": 5,
@@ -132,7 +134,9 @@ Parameters:
"merge_when_build_succeeds": true,
"merge_status": "can_be_merged",
"subscribed" : true,
- "user_notes_count": 1
+ "user_notes_count": 1,
+ "should_remove_source_branch": true,
+ "force_remove_source_branch": false
}
```
@@ -233,6 +237,8 @@ Parameters:
"merge_status": "can_be_merged",
"subscribed" : true,
"user_notes_count": 1,
+ "should_remove_source_branch": true,
+ "force_remove_source_branch": false,
"changes": [
{
"old_path": "VERSION",
@@ -296,7 +302,7 @@ Parameters:
"source_project_id": 4,
"target_project_id": 4,
"labels": [ ],
- "description":"fixed login page css paddings",
+ "description": "fixed login page css paddings",
"work_in_progress": false,
"milestone": {
"id": 5,
@@ -312,7 +318,9 @@ Parameters:
"merge_when_build_succeeds": true,
"merge_status": "can_be_merged",
"subscribed" : true,
- "user_notes_count": 0
+ "user_notes_count": 0,
+ "should_remove_source_branch": true,
+ "force_remove_source_branch": false
}
```
@@ -383,7 +391,9 @@ Parameters:
"merge_when_build_succeeds": true,
"merge_status": "can_be_merged",
"subscribed" : true,
- "user_notes_count": 1
+ "user_notes_count": 1,
+ "should_remove_source_branch": true,
+ "force_remove_source_branch": false
}
```
@@ -433,7 +443,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
@@ -465,7 +475,7 @@ Parameters:
"source_project_id": 4,
"target_project_id": 4,
"labels": [ ],
- "description":"fixed login page css paddings",
+ "description": "fixed login page css paddings",
"work_in_progress": false,
"milestone": {
"id": 5,
@@ -481,7 +491,9 @@ Parameters:
"merge_when_build_succeeds": true,
"merge_status": "can_be_merged",
"subscribed" : true,
- "user_notes_count": 1
+ "user_notes_count": 1,
+ "should_remove_source_branch": true,
+ "force_remove_source_branch": false
}
```
@@ -531,7 +543,7 @@ Parameters:
"source_project_id": 4,
"target_project_id": 4,
"labels": [ ],
- "description":"fixed login page css paddings",
+ "description": "fixed login page css paddings",
"work_in_progress": false,
"milestone": {
"id": 5,
@@ -547,7 +559,9 @@ Parameters:
"merge_when_build_succeeds": true,
"merge_status": "can_be_merged",
"subscribed" : true,
- "user_notes_count": 1
+ "user_notes_count": 1,
+ "should_remove_source_branch": true,
+ "force_remove_source_branch": false
}
```
@@ -776,3 +790,103 @@ 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,
+ "should_remove_source_branch": true,
+ "force_remove_source_branch": false
+ },
+ "target_url": "https://gitlab.example.com/gitlab-org/gitlab-ci/merge_requests/7",
+ "body": "Et voluptas laudantium minus nihil recusandae ut accusamus earum aut non.",
+ "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 f5f195b97df..dceee7b4ea7 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -52,7 +52,7 @@ Parameters:
"owner": {
"id": 3,
"name": "Diaspora",
- "created_at": "2013-09-30T13: 46: 02Z"
+ "created_at": "2013-09-30T13:46:02Z"
},
"name": "Diaspora Client",
"name_with_namespace": "Diaspora / Diaspora Client",
@@ -64,17 +64,18 @@ Parameters:
"builds_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
- "created_at": "2013-09-30T13: 46: 02Z",
- "last_activity_at": "2013-09-30T13: 46: 02Z",
+ "container_registry_enabled": false,
+ "created_at": "2013-09-30T13:46:02Z",
+ "last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3,
"namespace": {
- "created_at": "2013-09-30T13: 46: 02Z",
+ "created_at": "2013-09-30T13:46:02Z",
"description": "",
"id": 3,
"name": "Diaspora",
"owner_id": 1,
"path": "diaspora",
- "updated_at": "2013-09-30T13: 46: 02Z"
+ "updated_at": "2013-09-30T13:46:02Z"
},
"archived": false,
"avatar_url": "http://example.com/uploads/project/avatar/4/uploads/avatar.png",
@@ -82,7 +83,8 @@ Parameters:
"forks_count": 0,
"star_count": 0,
"runners_token": "b8547b1dc37721d05889db52fa2f02",
- "public_builds": true
+ "public_builds": true,
+ "shared_with_groups": []
},
{
"id": 6,
@@ -112,6 +114,7 @@ Parameters:
"builds_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
+ "container_registry_enabled": false,
"created_at": "2013-09-30T13:46:02Z",
"last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3,
@@ -140,7 +143,8 @@ Parameters:
"forks_count": 0,
"star_count": 0,
"runners_token": "b8547b1dc37721d05889db52fa2f02",
- "public_builds": true
+ "public_builds": true,
+ "shared_with_groups": []
}
]
```
@@ -223,7 +227,7 @@ Parameters:
"owner": {
"id": 3,
"name": "Diaspora",
- "created_at": "2013-09-30T13: 46: 02Z"
+ "created_at": "2013-09-30T13:46:02Z"
},
"name": "Diaspora Project Site",
"name_with_namespace": "Diaspora / Diaspora Project Site",
@@ -235,17 +239,18 @@ Parameters:
"builds_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
- "created_at": "2013-09-30T13: 46: 02Z",
- "last_activity_at": "2013-09-30T13: 46: 02Z",
+ "container_registry_enabled": false,
+ "created_at": "2013-09-30T13:46:02Z",
+ "last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3,
"namespace": {
- "created_at": "2013-09-30T13: 46: 02Z",
+ "created_at": "2013-09-30T13:46:02Z",
"description": "",
"id": 3,
"name": "Diaspora",
"owner_id": 1,
"path": "diaspora",
- "updated_at": "2013-09-30T13: 46: 02Z"
+ "updated_at": "2013-09-30T13:46:02Z"
},
"permissions": {
"project_access": {
@@ -262,7 +267,20 @@ Parameters:
"shared_runners_enabled": true,
"forks_count": 0,
"star_count": 0,
- "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b"
+ "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b",
+ "public_builds": true,
+ "shared_with_groups": [
+ {
+ "group_id": 4,
+ "group_name": "Twitter",
+ "group_access_level": 30
+ },
+ {
+ "group_id": 3,
+ "group_name": "Gitlab Org",
+ "group_access_level": 10
+ }
+ ]
}
```
@@ -425,6 +443,7 @@ Parameters:
- `wiki_enabled` (optional)
- `snippets_enabled` (optional)
- `container_registry_enabled` (optional)
+- `shared_runners_enabled` (optional)
- `public` (optional) - if `true` same as setting visibility_level = 20
- `visibility_level` (optional)
- `import_url` (optional)
@@ -449,6 +468,7 @@ Parameters:
- `wiki_enabled` (optional)
- `snippets_enabled` (optional)
- `container_registry_enabled` (optional)
+- `shared_runners_enabled` (optional)
- `public` (optional) - if `true` same as setting visibility_level = 20
- `visibility_level` (optional)
- `import_url` (optional)
@@ -475,6 +495,7 @@ Parameters:
- `wiki_enabled` (optional)
- `snippets_enabled` (optional)
- `container_registry_enabled` (optional)
+- `shared_runners_enabled` (optional)
- `public` (optional) - if `true` same as setting visibility_level = 20
- `visibility_level` (optional)
- `public_builds` (optional)
@@ -537,23 +558,26 @@ Example response:
"builds_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
- "created_at": "2013-09-30T13: 46: 02Z",
- "last_activity_at": "2013-09-30T13: 46: 02Z",
+ "container_registry_enabled": false,
+ "created_at": "2013-09-30T13:46:02Z",
+ "last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3,
"namespace": {
- "created_at": "2013-09-30T13: 46: 02Z",
+ "created_at": "2013-09-30T13:46:02Z",
"description": "",
"id": 3,
"name": "Diaspora",
"owner_id": 1,
"path": "diaspora",
- "updated_at": "2013-09-30T13: 46: 02Z"
+ "updated_at": "2013-09-30T13:46:02Z"
},
"archived": true,
"avatar_url": "http://example.com/uploads/project/avatar/3/uploads/avatar.png",
"shared_runners_enabled": true,
"forks_count": 0,
- "star_count": 1
+ "star_count": 1,
+ "public_builds": true,
+ "shared_with_groups": []
}
```
@@ -600,23 +624,26 @@ Example response:
"builds_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
- "created_at": "2013-09-30T13: 46: 02Z",
- "last_activity_at": "2013-09-30T13: 46: 02Z",
+ "container_registry_enabled": false,
+ "created_at": "2013-09-30T13:46:02Z",
+ "last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3,
"namespace": {
- "created_at": "2013-09-30T13: 46: 02Z",
+ "created_at": "2013-09-30T13:46:02Z",
"description": "",
"id": 3,
"name": "Diaspora",
"owner_id": 1,
"path": "diaspora",
- "updated_at": "2013-09-30T13: 46: 02Z"
+ "updated_at": "2013-09-30T13:46:02Z"
},
"archived": true,
"avatar_url": "http://example.com/uploads/project/avatar/3/uploads/avatar.png",
"shared_runners_enabled": true,
"forks_count": 0,
- "star_count": 0
+ "star_count": 0,
+ "public_builds": true,
+ "shared_with_groups": []
}
```
@@ -660,7 +687,7 @@ Example response:
"owner": {
"id": 3,
"name": "Diaspora",
- "created_at": "2013-09-30T13: 46: 02Z"
+ "created_at": "2013-09-30T13:46:02Z"
},
"name": "Diaspora Project Site",
"name_with_namespace": "Diaspora / Diaspora Project Site",
@@ -672,17 +699,18 @@ Example response:
"builds_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
- "created_at": "2013-09-30T13: 46: 02Z",
- "last_activity_at": "2013-09-30T13: 46: 02Z",
+ "container_registry_enabled": false,
+ "created_at": "2013-09-30T13:46:02Z",
+ "last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3,
"namespace": {
- "created_at": "2013-09-30T13: 46: 02Z",
+ "created_at": "2013-09-30T13:46:02Z",
"description": "",
"id": 3,
"name": "Diaspora",
"owner_id": 1,
"path": "diaspora",
- "updated_at": "2013-09-30T13: 46: 02Z"
+ "updated_at": "2013-09-30T13:46:02Z"
},
"permissions": {
"project_access": {
@@ -699,7 +727,9 @@ Example response:
"shared_runners_enabled": true,
"forks_count": 0,
"star_count": 0,
- "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b"
+ "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b",
+ "public_builds": true,
+ "shared_with_groups": []
}
```
@@ -713,7 +743,7 @@ have the proper access rights, code 403 is returned. Status 404 is returned if t
doesn't exist, or is hidden to the user.
```
-POST /projects/:id/archive
+POST /projects/:id/unarchive
```
| Attribute | Type | Required | Description |
@@ -743,7 +773,7 @@ Example response:
"owner": {
"id": 3,
"name": "Diaspora",
- "created_at": "2013-09-30T13: 46: 02Z"
+ "created_at": "2013-09-30T13:46:02Z"
},
"name": "Diaspora Project Site",
"name_with_namespace": "Diaspora / Diaspora Project Site",
@@ -755,17 +785,18 @@ Example response:
"builds_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
- "created_at": "2013-09-30T13: 46: 02Z",
- "last_activity_at": "2013-09-30T13: 46: 02Z",
+ "container_registry_enabled": false,
+ "created_at": "2013-09-30T13:46:02Z",
+ "last_activity_at": "2013-09-30T13:46:02Z",
"creator_id": 3,
"namespace": {
- "created_at": "2013-09-30T13: 46: 02Z",
+ "created_at": "2013-09-30T13:46:02Z",
"description": "",
"id": 3,
"name": "Diaspora",
"owner_id": 1,
"path": "diaspora",
- "updated_at": "2013-09-30T13: 46: 02Z"
+ "updated_at": "2013-09-30T13:46:02Z"
},
"permissions": {
"project_access": {
@@ -782,7 +813,9 @@ Example response:
"shared_runners_enabled": true,
"forks_count": 0,
"star_count": 0,
- "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b"
+ "runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b",
+ "public_builds": true,
+ "shared_with_groups": []
}
```
@@ -965,11 +998,11 @@ Parameters:
"id": 1,
"url": "http://example.com/hook",
"project_id": 3,
- "push_events": "true",
- "issues_events": "true",
- "merge_requests_events": "true",
- "note_events": "true",
- "enable_ssl_verification": "true",
+ "push_events": true,
+ "issues_events": true,
+ "merge_requests_events": true,
+ "note_events": true,
+ "enable_ssl_verification": true,
"created_at": "2012-10-12T17:04:47Z"
}
```
@@ -1089,8 +1122,8 @@ Parameters:
"name": "Jeremy Ashkenas",
"email": "jashkenas@example.com"
},
- "authored_date": "2013-09-07T12: 58: 21+00: 00",
- "committed_date": "2013-09-07T12: 58: 21+00: 00"
+ "authored_date": "2013-09-07T12:58:21+00:00",
+ "committed_date": "2013-09-07T12:58:21+00:00"
},
"protected": false
}
diff --git a/doc/api/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..937c71de386
--- /dev/null
+++ b/doc/api/todos.md
@@ -0,0 +1,296 @@
+# 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`, `marked`, or `approval_required`. |
+| `author_id` | integer | no | The ID of an author |
+| `project_id` | integer | no | The ID of a project |
+| `state` | string | no | The state of the todo. Can be either `pending` or `done` |
+| `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. It returns the number of marked todos.
+
+```
+DELETE /todos
+```
+
+```bash
+curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/todos
+```
+
+Example Response:
+
+```json
+3
+```
+
+[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 ef72df97ce6..0833027f91d 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -5,6 +5,8 @@
- [Get started with GitLab CI](quick_start/README.md)
- [CI examples for various languages](examples/README.md)
- [Learn how to enable or disable GitLab CI](enable_or_disable_ci.md)
+- [Pipelines and builds](pipelines.md)
+- [Environments and deployments](environments.md)
- [Learn how `.gitlab-ci.yml` works](yaml/README.md)
- [Configure a Runner, the application that runs your builds](runners/README.md)
- [Use Docker images with GitLab Runner](docker/using_docker_images.md)
@@ -13,6 +15,6 @@
- [Use SSH keys in your build environment](ssh_keys/README.md)
- [Trigger builds through the API](triggers/README.md)
- [Build artifacts](build_artifacts/README.md)
-- [User permissions](permissions/README.md)
-- [API](../../api/ci/README.md)
+- [User permissions](../user/permissions.md#gitlab-ci)
+- [API](../api/ci/README.md)
- [CI services (linked docker containers)](services/README.md)
diff --git a/doc/ci/build_artifacts/img/build_artifacts_browser.png b/doc/ci/build_artifacts/img/build_artifacts_browser.png
index 73ed4eeb927..59cf2b8746b 100644
--- a/doc/ci/build_artifacts/img/build_artifacts_browser.png
+++ b/doc/ci/build_artifacts/img/build_artifacts_browser.png
Binary files differ
diff --git a/doc/ci/build_artifacts/img/build_artifacts_browser_button.png b/doc/ci/build_artifacts/img/build_artifacts_browser_button.png
index 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
Binary files differ
diff --git a/doc/ci/environments.md b/doc/ci/environments.md
new file mode 100644
index 00000000000..d85b8a34ced
--- /dev/null
+++ b/doc/ci/environments.md
@@ -0,0 +1,58 @@
+# Introduction to environments and deployments
+
+>**Note:**
+Introduced in GitLab 8.9.
+
+## Environments
+
+Environments are places where code gets deployed, such as staging or production.
+CI/CD [Pipelines] usually have one or more [jobs] that deploy to an environment.
+Defining environments in a project's `.gitlab-ci.yml` lets developers track
+[deployments] to these environments.
+
+## Deployments
+
+Deployments are created when [jobs] deploy versions of code to [environments].
+
+## Defining environments
+
+You can create and delete environments manually in the web interface, but we
+recommend that you define your environments in `.gitlab-ci.yml` first, which
+will automatically create environments for you after the first deploy.
+
+The `environment` is just a hint for GitLab that this job actually deploys to
+this environment. Each time the job succeeds, a deployment is recorded,
+remembering the git SHA and environment.
+
+Add something like this to your `.gitlab-ci.yml`:
+```
+production:
+ stage: deploy
+ script: dpl...
+ environment: production
+```
+
+See full [documentation](yaml/README.md#environment).
+
+## Seeing environment status
+
+You can find the environment list under **Pipelines > Environments** for your
+project. You'll see the git SHA and date of the last deployment to each
+environment defined.
+
+>**Note:**
+Only deploys that happen after your `.gitlab-ci.yml` is properly configured will
+show up in the environments and deployments lists.
+
+## Seeing deployment history
+
+Clicking on an environment will show the history of deployments.
+
+>**Note:**
+Only deploys that happen after your `.gitlab-ci.yml` is properly configured will
+show up in the environments and deployments lists.
+
+[Pipelines]: pipelines.md
+[jobs]: yaml/README.md#jobs
+[environments]: #environments
+[deployments]: #deployments
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
index d088b8b329d..35780e277ae 100644
--- a/doc/ci/img/builds_tab.png
+++ b/doc/ci/img/builds_tab.png
Binary files differ
diff --git a/doc/ci/img/features_settings.png b/doc/ci/img/features_settings.png
index 17aba5d14d8..38d7036f606 100644
--- a/doc/ci/img/features_settings.png
+++ b/doc/ci/img/features_settings.png
Binary files differ
diff --git a/doc/ci/permissions/README.md b/doc/ci/permissions/README.md
index d77061c14cd..42eb59f84c8 100644
--- a/doc/ci/permissions/README.md
+++ b/doc/ci/permissions/README.md
@@ -1,24 +1,3 @@
# Users Permissions
-GitLab CI relies on user's role on the GitLab. There are three permissions levels on GitLab CI: admin, master, developer, other.
-
-Admin user can perform any actions on GitLab CI in scope of instance and project. Also user with admin permission can use admin interface.
-
-
-
-
-| Action | Guest, Reporter | Developer | Master | Admin |
-|---------------------------------------|-----------------|-------------|----------|--------|
-| See commits and builds | ✓ | ✓ | ✓ | ✓ |
-| Retry or cancel build | | ✓ | ✓ | ✓ |
-| Remove project | | | ✓ | ✓ |
-| Create project | | | ✓ | ✓ |
-| Change project configuration | | | ✓ | ✓ |
-| Add specific runners | | | ✓ | ✓ |
-| Add shared runners | | | | ✓ |
-| See events in the system | | | | ✓ |
-| Admin interface | | | | ✓ |
-
-
-
-
+This document was moved to [user/permissions.md](../../user/permissions.md#gitlab-ci).
diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md
new file mode 100644
index 00000000000..48a9f994759
--- /dev/null
+++ b/doc/ci/pipelines.md
@@ -0,0 +1,38 @@
+# Introduction to pipelines and builds
+
+>**Note:**
+Introduced in GitLab 8.8.
+
+## Pipelines
+
+A pipeline is a group of [builds] that get executed in [stages] (batches). All
+of the builds in a stage are executed in parallel (if there are enough
+concurrent [runners]), and if they all succeed, the pipeline moves on to the
+next stage. If one of the builds fails, the next stage is not (usually)
+executed.
+
+## Builds
+
+Builds are individual runs of [jobs]. Not to be confused with a `build` job or
+`build` stage.
+
+## Defining pipelines
+
+Pipelines are defined in `.gitlab-ci.yml` by specifying [jobs] that run in
+[stages].
+
+See full [documentation](yaml/README.md#jobs).
+
+## Seeing pipeline status
+
+You can find the current and historical pipeline runs under **Pipelines** for your
+project.
+
+## Seeing build status
+
+Clicking on a pipeline will show the builds that were run for that pipeline.
+
+[builds]: #builds
+[jobs]: yaml/README.md#jobs
+[stages]: yaml/README.md#stages
+[runners]: runners/README.md
diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md
index 386b8e29fcf..7fa1a478f34 100644
--- a/doc/ci/quick_start/README.md
+++ b/doc/ci/quick_start/README.md
@@ -4,41 +4,41 @@
is fully integrated into GitLab itself and is [enabled] by default on all
projects.
-The TL;DR version of how GitLab CI works is the following.
-
----
-
GitLab offers a [continuous integration][ci] service. If you
[add a `.gitlab-ci.yml` file][yaml] to the root directory of your repository,
and configure your GitLab project to use a [Runner], then each merge request or
-push triggers a build.
+push triggers your CI [pipeline].
-The `.gitlab-ci.yml` file tells the GitLab runner what to do. By default it
-runs three [stages]: `build`, `test`, and `deploy`.
+The `.gitlab-ci.yml` file tells the GitLab runner what to do. By default it runs
+a pipeline with three [stages]: `build`, `test`, and `deploy`. You don't need to
+use all three stages; stages with no jobs are simply ignored.
If everything runs OK (no non-zero return values), you'll get a nice green
checkmark associated with the pushed commit or merge request. This makes it
-easy to see whether a merge request will cause any of the tests to fail before
+easy to see whether a merge request caused any of the tests to fail before
you even look at the code.
-Most projects only use GitLab's CI service to run the test suite so that
+Most projects use GitLab's CI service to run the test suite so that
developers get immediate feedback if they broke something.
+There's a growing trend to use continuous delivery and continuous deployment to
+automatically deploy tested code to staging and production environments.
+
So in brief, the steps needed to have a working CI can be summed up to:
1. Add `.gitlab-ci.yml` to the root directory of your repository
1. Configure a Runner
-From there on, on every push to your Git repository, the build will be
-automagically started by the Runner and will appear under the project's
-`/builds` page.
+From there on, on every push to your Git repository, the Runner will
+automagically start the pipeline and the pipeline will appear under the
+project's `/pipelines` page.
---
This guide assumes that you:
- have a working GitLab instance of version 8.0 or higher or are using
- [GitLab.com](https://gitlab.com/users/sign_in)
+ [GitLab.com](https://gitlab.com)
- have a project in GitLab that you would like to use CI for
Let's break it down to pieces and work on solving the GitLab CI puzzle.
@@ -57,15 +57,14 @@ On any push to your repository, GitLab will look for the `.gitlab-ci.yml`
file and start builds on _Runners_ according to the contents of the file,
for that commit.
-Because `.gitlab-ci.yml` is in the repository, it is version controlled,
-old versions still build successfully, forks can easily make use of CI,
-branches can have separate builds and you have a single source of truth for CI.
-You can read more about the reasons why we are using `.gitlab-ci.yml`
-[in our blog about it][blog-ci].
+Because `.gitlab-ci.yml` is in the repository and is version controlled, old
+versions still build successfully, forks can easily make use of CI, branches can
+have different pipelines and jobs, and you have a single source of truth for CI.
+You can read more about the reasons why we are using `.gitlab-ci.yml` [in our
+blog about it][blog-ci].
**Note:** `.gitlab-ci.yml` is a [YAML](https://en.wikipedia.org/wiki/YAML) file
-so you have to pay extra attention to the indentation. Always use spaces, not
-tabs.
+so you have to pay extra attention to indentation. Always use spaces, not tabs.
### Creating a simple `.gitlab-ci.yml` file
@@ -108,7 +107,7 @@ If you want to check whether your `.gitlab-ci.yml` file is valid, there is a
Lint tool under the page `/ci/lint` of your GitLab instance. You can also find
the link under **Settings > CI settings** in your project.
-For more information and a complete `.gitlab-ci.yml` syntax, please check
+For more information and a complete `.gitlab-ci.yml` syntax, please read
[the documentation on .gitlab-ci.yml](../yaml/README.md).
### Push `.gitlab-ci.yml` to GitLab
@@ -122,7 +121,8 @@ git commit -m "Add .gitlab-ci.yml"
git push origin master
```
-Now if you go to the **Builds** page you will see that the builds are pending.
+Now if you go to the **Pipelines** page you will see that the pipeline is
+pending.
You can also go to the **Commits** page and notice the little clock icon next
to the commit SHA.
@@ -138,15 +138,14 @@ Notice that there are two jobs pending which are named after what we wrote in
`.gitlab-ci.yml`. The red triangle indicates that there is no Runner configured
yet for these builds.
-The next step is to configure a Runner so that it picks the pending jobs.
+The next step is to configure a Runner so that it picks the pending builds.
## Configuring a Runner
-In GitLab, Runners run the builds that you define in `.gitlab-ci.yml`.
-A Runner can be a virtual machine, a VPS, a bare-metal machine, a docker
-container or even a cluster of containers. GitLab and the Runners communicate
-through an API, so the only needed requirement is that the machine on which the
-Runner is configured to have Internet access.
+In GitLab, Runners run the builds that you define in `.gitlab-ci.yml`. A Runner
+can be a virtual machine, a VPS, a bare-metal machine, a docker container or
+even a cluster of containers. GitLab and the Runners communicate through an API,
+so the only requirement is that the Runner's machine has Internet access.
A Runner can be specific to a certain project or serve multiple projects in
GitLab. If it serves all projects it's called a _Shared Runner_.
@@ -188,12 +187,16 @@ To enable **Shared Runners** you have to go to your project's
[Read more on Shared Runners](../runners/README.md).
-## Seeing the status of your build
+## Seeing the status of your pipeline and builds
After configuring the Runner successfully, you should see the status of your
last commit change from _pending_ to either _running_, _success_ or _failed_.
-You can view all builds, by going to the **Builds** page in your project.
+You can view all pipelines by going to the **Pipelines** page in your project.
+
+![Commit status](img/pipelines_status.png)
+
+Or you can view all builds, by going to the **Pipelines > Builds** page.
![Commit status](img/builds_status.png)
@@ -238,3 +241,4 @@ CI with various languages.
[runner]: ../runners/README.md
[enabled]: ../enable_or_disable_ci.md
[stages]: ../yaml/README.md#stages
+[pipeline]: ../pipelines.md
diff --git a/doc/ci/quick_start/img/build_log.png b/doc/ci/quick_start/img/build_log.png
index 89e6cd40cb6..b53a6cd86b0 100644
--- a/doc/ci/quick_start/img/build_log.png
+++ b/doc/ci/quick_start/img/build_log.png
Binary files differ
diff --git a/doc/ci/quick_start/img/builds_status.png b/doc/ci/quick_start/img/builds_status.png
index b8e6c2a361a..47862761ffe 100644
--- a/doc/ci/quick_start/img/builds_status.png
+++ b/doc/ci/quick_start/img/builds_status.png
Binary files differ
diff --git a/doc/ci/quick_start/img/new_commit.png b/doc/ci/quick_start/img/new_commit.png
index 3d3c9d5c0bd..a53562ce328 100644
--- a/doc/ci/quick_start/img/new_commit.png
+++ b/doc/ci/quick_start/img/new_commit.png
Binary files differ
diff --git a/doc/ci/quick_start/img/pipelines_status.png b/doc/ci/quick_start/img/pipelines_status.png
new file mode 100644
index 00000000000..6bc97bb739c
--- /dev/null
+++ b/doc/ci/quick_start/img/pipelines_status.png
Binary files differ
diff --git a/doc/ci/quick_start/img/runners_activated.png b/doc/ci/quick_start/img/runners_activated.png
index eafcfd6ecd5..23261123b18 100644
--- a/doc/ci/quick_start/img/runners_activated.png
+++ b/doc/ci/quick_start/img/runners_activated.png
Binary files differ
diff --git a/doc/ci/quick_start/img/single_commit_status_pending.png b/doc/ci/quick_start/img/single_commit_status_pending.png
index 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
Binary files differ
diff --git a/doc/ci/quick_start/img/status_pending.png b/doc/ci/quick_start/img/status_pending.png
index a049ec2a5ba..9feacf0c961 100644
--- a/doc/ci/quick_start/img/status_pending.png
+++ b/doc/ci/quick_start/img/status_pending.png
Binary files differ
diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md
index 400784da617..ddebd987650 100644
--- a/doc/ci/runners/README.md
+++ b/doc/ci/runners/README.md
@@ -96,6 +96,12 @@ To register the runner, run the command below and follow instructions:
sudo gitlab-ci-multi-runner register
```
+### Lock a specific runner from being enabled for other projects
+
+You can configure a runner to assign it exclusively to a project. When a
+runner is locked this way, it can no longer be enabled for other projects.
+This setting is available on each runner in *Project Settings* > *Runners*.
+
### Making an existing Shared Runner Specific
If you are an admin on your GitLab instance,
@@ -128,7 +134,7 @@ the appropriate dependencies to run Rails test suites.
### Prevent runner with tags from picking jobs without tags
You can configure a runner to prevent it from picking jobs with tags when
-the runnner does not have tags assigned. This setting is available on each
+the runner does not have tags assigned. This setting is available on each
runner in *Project Settings* > *Runners*.
### Be careful with sensitive information
diff --git a/doc/ci/runners/project_specific.png b/doc/ci/runners/project_specific.png
index f51ea694e78..c812defa67b 100644
--- a/doc/ci/runners/project_specific.png
+++ b/doc/ci/runners/project_specific.png
Binary files differ
diff --git a/doc/ci/runners/shared_runner.png b/doc/ci/runners/shared_runner.png
index 9755144eb08..31574a17764 100644
--- a/doc/ci/runners/shared_runner.png
+++ b/doc/ci/runners/shared_runner.png
Binary files differ
diff --git a/doc/ci/runners/shared_to_specific_admin.png b/doc/ci/runners/shared_to_specific_admin.png
index 44a4bef22f7..8f4010a5849 100644
--- a/doc/ci/runners/shared_to_specific_admin.png
+++ b/doc/ci/runners/shared_to_specific_admin.png
Binary files differ
diff --git a/doc/ci/triggers/img/builds_page.png b/doc/ci/triggers/img/builds_page.png
index e78794fbee7..2dee8ee6107 100644
--- a/doc/ci/triggers/img/builds_page.png
+++ b/doc/ci/triggers/img/builds_page.png
Binary files differ
diff --git a/doc/ci/triggers/img/trigger_single_build.png b/doc/ci/triggers/img/trigger_single_build.png
index c25f27409d6..baf3fc183d8 100644
--- a/doc/ci/triggers/img/trigger_single_build.png
+++ b/doc/ci/triggers/img/trigger_single_build.png
Binary files differ
diff --git a/doc/ci/triggers/img/trigger_variables.png b/doc/ci/triggers/img/trigger_variables.png
index 2207e8b34cb..908355c33a5 100644
--- a/doc/ci/triggers/img/trigger_variables.png
+++ b/doc/ci/triggers/img/trigger_variables.png
Binary files differ
diff --git a/doc/ci/triggers/img/triggers_page.png b/doc/ci/triggers/img/triggers_page.png
index 268368dc3c5..69cec5cdebf 100644
--- a/doc/ci/triggers/img/triggers_page.png
+++ b/doc/ci/triggers/img/triggers_page.png
Binary files differ
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 9c98f9c98c6..31b4fd2669e 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -13,32 +13,34 @@ If you want a quick introduction to GitLab CI, follow our
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
- [.gitlab-ci.yml](#gitlab-ci-yml)
- - [image and services](#image-and-services)
- - [before_script](#before_script)
- - [after_script](#after_script)
- - [stages](#stages)
- - [types](#types)
- - [variables](#variables)
- - [cache](#cache)
- - [cache:key](#cache-key)
+ - [image and services](#image-and-services)
+ - [before_script](#before_script)
+ - [after_script](#after_script)
+ - [stages](#stages)
+ - [types](#types)
+ - [variables](#variables)
+ - [cache](#cache)
+ - [cache:key](#cache-key)
- [Jobs](#jobs)
- - [script](#script)
- - [stage](#stage)
- - [job variables](#job-variables)
- - [only and except](#only-and-except)
- - [tags](#tags)
- - [when](#when)
- - [environment](#environment)
- - [artifacts](#artifacts)
- - [artifacts:name](#artifacts-name)
- - [artifacts:when](#artifacts-when)
- - [artifacts:expire_in](#artifacts-expire_in)
- - [dependencies](#dependencies)
- - [before_script and after_script](#before_script-and-after_script)
+ - [script](#script)
+ - [stage](#stage)
+ - [only and except](#only-and-except)
+ - [job variables](#job-variables)
+ - [tags](#tags)
+ - [when](#when)
+ - [environment](#environment)
+ - [artifacts](#artifacts)
+ - [artifacts:name](#artifactsname)
+ - [artifacts:when](#artifactswhen)
+ - [artifacts:expire_in](#artifactsexpire_in)
+ - [dependencies](#dependencies)
+ - [before_script and after_script](#before_script-and-after_script)
+- [Git Strategy](#git-strategy)
+- [Shallow cloning](#shallow-cloning)
- [Hidden jobs](#hidden-jobs)
- [Special YAML features](#special-yaml-features)
- - [Anchors](#anchors)
-- [Validate the .gitlab-ci.yml](#validate-the-gitlab-ci-yml)
+ - [Anchors](#anchors)
+- [Validate the .gitlab-ci.yml](#validate-the-gitlab-ciyml)
- [Skipping builds](#skipping-builds)
- [Examples](#examples)
@@ -54,7 +56,7 @@ of your repository and contains definitions of how your project should be built.
The YAML file defines a set of jobs with constraints stating when they should
be run. The jobs are defined as top-level elements with a name and always have
-to contain the `script` clause:
+to contain at least the `script` clause:
```yaml
job1:
@@ -131,7 +133,7 @@ builds, including deploy builds. This can be an array or a multi-line string.
### after_script
>**Note:**
-Introduced in GitLab 8.7 and requires Gitlab Runner v1.2 (not yet released)
+Introduced in GitLab 8.7 and requires Gitlab Runner v1.2
`after_script` is used to define the command that will be run after for all
builds. This has to be an array or a multi-line string.
@@ -165,9 +167,9 @@ stages:
There are also two edge cases worth mentioning:
-1. If no `stages` is defined in `.gitlab-ci.yml`, then by default the `build`,
+1. If no `stages` are defined in `.gitlab-ci.yml`, then by default the `build`,
`test` and `deploy` are allowed to be used as job's stage by default.
-2. If a job doesn't specify `stage`, the job is assigned the `test` stage.
+2. If a job doesn't specify a `stage`, the job is assigned the `test` stage.
### types
@@ -178,9 +180,9 @@ Alias for [stages](#stages).
>**Note:**
Introduced in GitLab Runner v0.5.0.
-GitLab CI allows you to add to `.gitlab-ci.yml` variables that are set in build
-environment. The variables are stored in the git repository and are meant to
-store non-sensitive project configuration, for example:
+GitLab CI allows you to add variables to `.gitlab-ci.yml` that are set in the
+build environment. The variables are stored in the git repository and are meant
+to store non-sensitive project configuration, for example:
```yaml
variables:
@@ -253,8 +255,8 @@ rspec:
- binaries/
```
-The cache is provided on best effort basis, so don't expect that cache will be
-always present. For implementation details please check GitLab Runner.
+The cache is provided on a best-effort basis, so don't expect that the cache
+will be always present. For implementation details, please check GitLab Runner.
#### cache:key
@@ -479,10 +481,11 @@ failure.
`when` can be set to one of the following values:
1. `on_success` - execute build only when all builds from prior stages
- succeeded. This is the default.
+ succeed. This is the default.
1. `on_failure` - execute build only when at least one build from prior stages
- failed.
-1. `always` - execute build despite the status of builds from prior stages.
+ fails.
+1. `always` - execute build regardless of the status of builds from prior stages.
+1. `manual` - execute build manually.
For example:
@@ -514,6 +517,7 @@ deploy_job:
stage: deploy
script:
- make deploy
+ when: manual
cleanup_job:
stage: cleanup
@@ -525,19 +529,36 @@ cleanup_job:
The above script will:
1. Execute `cleanup_build_job` only when `build_job` fails
-2. Always execute `cleanup_job` as the last step in pipeline.
+2. Always execute `cleanup_job` as the last step in pipeline
+3. Allow you to manually execute `deploy_job` from GitLab
+
+#### Manual actions
+
+>**Note:**
+Introduced in GitLab 8.10.
+
+Manual actions are special type of jobs that are not executed automatically in pipeline.
+They need to be explicitly started by the user.
+Manual actions can be started from pipelines, builds, environments and deployments views.
+You can execute the same manual action multiple times.
+
+Example usage of manual actions is deployment, ex. promote a staging environment to production.
### environment
>**Note:**
-Introduced in GitLab v8.9.0.
+Introduced in GitLab 8.9.
-`environment` is used to define that job does deployment to specific environment.
-This allows to easily track all deployments to your environments straight from GitLab.
+`environment` is used to define that a job deploys to a specific environment.
+This allows easy tracking of all deployments to your environments straight from
+GitLab.
-If `environment` is specified and no environment under that name does exist a new one will be created automatically.
+If `environment` is specified and no environment under that name exists, a new
+one will be created automatically.
-The `environment` name must contain only letters, digits, '-' and '_'.
+The `environment` name must contain only letters, digits, '-' and '_'. Common
+names are `qa`, `staging`, and `production`, but you can use whatever name works
+with your workflow.
---
@@ -550,7 +571,8 @@ deploy to production:
environment: production
```
-The `deploy to production` job will be marked as doing deployment to `production` environment.
+The `deploy to production` job will be marked as doing deployment to
+`production` environment.
### artifacts
@@ -559,10 +581,10 @@ The `deploy to production` job will be marked as doing deployment to `production
> - Introduced in GitLab Runner v0.7.0 for non-Windows platforms.
> - Windows support was added in GitLab Runner v.1.0.0.
> - Currently not all executors are supported.
-> - Build artifacts are only collected for successful builds.
+> - Build artifacts are only collected for successful builds by default.
-`artifacts` is used to specify list of files and directories which should be
-attached to build after success. To pass artifacts between different builds,
+`artifacts` is used to specify a list of files and directories which should be
+attached to the build after success. To pass artifacts between different builds,
see [dependencies](#dependencies).
Below are some examples.
@@ -690,9 +712,9 @@ failure.
`artifacts:when` can be set to one of the following values:
-1. `on_success` - upload artifacts only when build succeeds. This is the default
-1. `on_failure` - upload artifacts only when build fails
-1. `always` - upload artifacts despite the build status
+1. `on_success` - upload artifacts only when the build succeeds. This is the default.
+1. `on_failure` - upload artifacts only when the build fails.
+1. `always` - upload artifacts regardless of the build status.
---
@@ -711,16 +733,18 @@ job:
>**Note:**
Introduced in GitLab 8.9 and GitLab Runner v1.3.0.
-`artifacts:expire_in` is used to remove uploaded artifacts after specified time.
-By default artifacts are stored on GitLab forver.
-`expire_in` allows to specify after what time the artifacts should be removed.
-The artifacts will expire counting from the moment when they are uploaded and stored on GitLab.
+`artifacts:expire_in` is used to delete uploaded artifacts after the specified
+time. By default, artifacts are stored on GitLab forever. `expire_in` allows you
+to specify how long artifacts should live before they expire, counting from the
+time they are uploaded and stored on GitLab.
-After artifacts uploading you can use the **Keep** button on build page to keep the artifacts forever.
+You can use the **Keep** button on the build page to override expiration and
+keep artifacts forever.
-Artifacts are removed every hour, but they are not accessible after expire date.
+After expiry, artifacts are actually deleted hourly by default (via a cron job),
+but they are not accessible after expiry.
-The value of `expire_in` is a elapsed time. The example of parsable values:
+The value of `expire_in` is an elapsed time. Examples of parseable values:
- '3 mins 4 sec'
- '2 hrs 20 min'
- '2h20min'
@@ -732,7 +756,7 @@ The value of `expire_in` is a elapsed time. The example of parsable values:
**Example configurations**
-To expire artifacts after 1 week from the moment that they are uploaded:
+To expire artifacts 1 week after being uploaded:
```yaml
job:
@@ -748,12 +772,13 @@ Introduced in GitLab 8.6 and GitLab Runner v1.1.1.
This feature should be used in conjunction with [`artifacts`](#artifacts) and
allows you to define the artifacts to pass between different builds.
-Note that `artifacts` from previous [stages](#stages) are passed by default.
+Note that `artifacts` from all previous [stages](#stages) are passed by default.
To use this feature, define `dependencies` in context of the job and pass
a list of all previous builds from which the artifacts should be downloaded.
You can only define builds from stages that are executed before the current one.
An error will be shown if you define builds from the current stage or next ones.
+Defining an empty array will skip downloading any artifacts for that job.
---
@@ -802,7 +827,7 @@ deploy:
It's possible to overwrite globally defined `before_script` and `after_script`:
```yaml
-before_script
+before_script:
- global before script
job:
@@ -814,6 +839,61 @@ job:
- execute this after my script
```
+## Git Strategy
+
+>**Note:**
+Introduced in GitLab 8.9 as an experimental feature. May change in future
+releases or be removed completely.
+
+You can set the `GIT_STRATEGY` used for getting recent application code. `clone`
+is slower, but makes sure you have a clean directory before every build. `fetch`
+is faster. `GIT_STRATEGY` can be specified in the global `variables` section or
+in the `variables` section for individual jobs. If it's not specified, then the
+default from project settings will be used.
+
+```
+variables:
+ GIT_STRATEGY: clone
+```
+
+or
+
+```
+variables:
+ GIT_STRATEGY: fetch
+```
+
+## Shallow cloning
+
+>**Note:**
+Introduced in GitLab 8.9 as an experimental feature. May change in future
+releases or be removed completely.
+
+You can specify the depth of fetching and cloning using `GIT_DEPTH`. This allows
+shallow cloning of the repository which can significantly speed up cloning for
+repositories with a large number of commits or old, large binaries. The value is
+passed to `git fetch` and `git clone`.
+
+>**Note:**
+If you use a depth of 1 and have a queue of builds or retry
+builds, jobs may fail.
+
+Since Git fetching and cloning is based on a ref, such as a branch name, runners
+can't clone a specific commit SHA. If there are multiple builds in the queue, or
+you are retrying an old build, the commit to be tested needs to be within the
+git history that is cloned. Setting too small a value for `GIT_DEPTH` can make
+it impossible to run these old commits. You will see `unresolved reference` in
+build logs. You should then reconsider changing `GIT_DEPTH` to a higher value.
+
+Builds that rely on `git describe` may not work correctly when `GIT_DEPTH` is
+set since only part of the git history is present.
+
+To fetch or clone only the last 3 commits:
+```
+variables:
+ GIT_DEPTH: "3"
+```
+
## Hidden jobs
>**Note:**
@@ -921,11 +1001,11 @@ directive defined in `.postgres_services` and `.mysql_services` respectively:
- ruby
test:postgres:
- << *job_definition
+ <<: *job_definition
services: *postgres_definition
test:mysql:
- << *job_definition
+ <<: *job_definition
services: *mysql_definition
```
@@ -970,8 +1050,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
index e9505a73b40..57d6f9f22c5 100644
--- a/doc/container_registry/img/container_registry.png
+++ b/doc/container_registry/img/container_registry.png
Binary files differ
diff --git a/doc/container_registry/img/project_feature.png b/doc/container_registry/img/project_feature.png
index 57a73d253c0..a59b4f82b56 100644
--- a/doc/container_registry/img/project_feature.png
+++ b/doc/container_registry/img/project_feature.png
Binary files differ
diff --git a/doc/customization/branded_login_page/appearance.png b/doc/customization/branded_login_page/appearance.png
index 6bce1f0a287..023dc5599b4 100644
--- a/doc/customization/branded_login_page/appearance.png
+++ b/doc/customization/branded_login_page/appearance.png
Binary files differ
diff --git a/doc/customization/branded_login_page/custom_sign_in.png b/doc/customization/branded_login_page/custom_sign_in.png
index d6020b029a2..7d99e0a2b3b 100644
--- a/doc/customization/branded_login_page/custom_sign_in.png
+++ b/doc/customization/branded_login_page/custom_sign_in.png
Binary files differ
diff --git a/doc/customization/branded_login_page/default_login_page.png b/doc/customization/branded_login_page/default_login_page.png
index 795c7954d8e..0cfa9da202e 100644
--- a/doc/customization/branded_login_page/default_login_page.png
+++ b/doc/customization/branded_login_page/default_login_page.png
Binary files differ
diff --git a/doc/customization/issue_closing.md b/doc/customization/issue_closing.md
index 194b8e00299..4620bb2dcde 100644
--- a/doc/customization/issue_closing.md
+++ b/doc/customization/issue_closing.md
@@ -8,7 +8,7 @@ the matched text will be closed. This happens when the commit is pushed to a pro
When not specified, the default `issue_closing_pattern` as shown below will be used:
```bash
-((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?))+)
+((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing))(:?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z][A-Z0-9_]+-\d+))+)
```
Here, `%{issue_ref}` is a complex regular expression defined inside GitLab, that matches a reference to a local issue (`#123`), cross-project issue (`group/project#123`) or a link to an issue (`https://gitlab.example.com/group/project/issues/123`).
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/doc_styleguide.md b/doc/development/doc_styleguide.md
index f5d97179f8a..fac35ec964d 100644
--- a/doc/development/doc_styleguide.md
+++ b/doc/development/doc_styleguide.md
@@ -44,7 +44,7 @@ it organized and easy to find.
- When introducing a new document, be careful for the headings to be
grammatically and syntactically correct. It is advised to mention one or all
of the following GitLab members for a review: `@axil`, `@rspeicher`,
- `@dblessing`, `@ashleys`, `@nearlythere`. This is to ensure that no document
+ `@dblessing`, `@ashleys`. This is to ensure that no document
with wrong heading is going live without an audit, thus preventing dead links
and redirection issues when corrected
- Leave exactly one newline after a heading
@@ -183,6 +183,62 @@ For example, if you were to move `doc/workflow/lfs/lfs_administration.md` to
(`workflow/lfs/lfs_administration.md`).
+## Configuration documentation for source and Omnibus installations
+
+GitLab currently officially supports two installation methods: installations
+from source and Omnibus packages installations.
+
+Whenever there is a setting that is configurable for both installation methods,
+prefer to document it in the CE docs to avoid duplication.
+
+Configuration settings include:
+
+- settings that touch configuration files in `config/`
+- NGINX settings and settings in `lib/support/` in general
+
+When there is a list of steps to perform, usually that entails editing the
+configuration file and reconfiguring/restarting GitLab. In such case, follow
+the style below as a guide:
+
+````
+**For Omnibus installations**
+
+1. Edit `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ external_url "https://gitlab.example.com"
+ ```
+
+1. Save the file and [reconfigure] GitLab for the changes to take effect.
+
+---
+
+**For installations from source**
+
+1. Edit `config/gitlab.yml`:
+
+ ```yaml
+ gitlab:
+ host: "gitlab.example.com"
+ ```
+
+1. Save the file and [restart] GitLab for the changes to take effect.
+
+
+[reconfigure]: path/to/administration/gitlab_restart.md#omnibus-gitlab-reconfigure
+[restart]: path/to/administration/gitlab_restart.md#installations-from-source
+````
+
+In this case:
+
+- before each step list the installation method is declared in bold
+- three dashes (`---`) are used to create an horizontal line and separate the
+ two methods
+- the code blocks are indented one or more spaces under the list item to render
+ correctly
+- different highlighting languages are used for each config in the code block
+- the [references](#references) guide is used for reconfigure/restart
+
## API
Here is a list of must-have items. Use them in the exact order that appears
diff --git a/doc/development/gitlab_architecture_diagram.png b/doc/development/gitlab_architecture_diagram.png
new file mode 100644
index 00000000000..80e975718e0
--- /dev/null
+++ b/doc/development/gitlab_architecture_diagram.png
Binary files differ
diff --git a/doc/development/gitlab_diagram_overview.png b/doc/development/gitlab_diagram_overview.png
deleted file mode 100644
index d9b9eed3d8f..00000000000
--- a/doc/development/gitlab_diagram_overview.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/gotchas.md b/doc/development/gotchas.md
index 21078c8d6f9..9d7fe7440d2 100644
--- a/doc/development/gotchas.md
+++ b/doc/development/gotchas.md
@@ -46,7 +46,7 @@ Rubocop](https://gitlab.com/gitlab-org/gitlab-ce/blob/8-4-stable/.rubocop.yml#L9
Using the inline `:coffee` or `:coffeescript` Haml filters comes with a
performance overhead.
-_**Note:** We've [removed these two filters](https://gitlab.com/gitlab-org/gitlab-ce/blob/8-5-stable/config/initializers/haml.rb)
+_**Note:** We've [removed these two filters](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/initializers/hamlit.rb)
in an initializer._
### Further reading
diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md
index 8a7547e5322..e2ca46504e7 100644
--- a/doc/development/migration_style_guide.md
+++ b/doc/development/migration_style_guide.md
@@ -37,7 +37,6 @@ First, you need to provide information on whether the migration can be applied:
For example:
```
-# rubocop:disable all
# Migration type: online without errors (works on previous version and new one)
class MyMigration < ActiveRecord::Migration
...
diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md
index 6d04b9590e6..8852dbcb19e 100644
--- a/doc/development/rake_tasks.md
+++ b/doc/development/rake_tasks.md
@@ -33,3 +33,28 @@ bundle exec rake gitlab:generate_docs
```
bundle exec rake services:doc
```
+
+## Updating Emoji Digests
+
+To update the Emoji digests file (used for Emoji autocomplete) you must run the
+following:
+
+```
+bundle exec rake gemojione:digests
+```
+
+This will update the file `fixtures/emojis/digests.json` based on the currently
+available Emoji.
+
+## Emoji Sprites
+
+Generating a sprite file containing all the Emoji can be done by running:
+
+```
+bundle exec rake gemojione:sprite
+```
+
+If new emoji are added, the spritesheet may change size. To compensate for
+such changes, first generate the `emoji.png` spritesheet with the above Rake
+task, then check the dimensions of the new spritesheet and update the
+`SPRITESHEET_WIDTH` and `SPRITESHEET_HEIGHT` constants accordingly.
diff --git a/doc/development/ui_guide.md b/doc/development/ui_guide.md
index 5893b7c219e..65252288019 100644
--- a/doc/development/ui_guide.md
+++ b/doc/development/ui_guide.md
@@ -1,43 +1,45 @@
-# UI Guide for building GitLab
+# UI Guide for building GitLab
## GitLab UI development kit
We created a page inside GitLab where you can check commonly used html and css elements.
-When you run GitLab instance locally - just visit http://localhost:3000/help/ui page to see UI examples
+When you run GitLab instance locally - just visit http://localhost:3000/help/ui page to see UI examples
you can use during GitLab development.
## Design repository
-All design files are stored in the [gitlab-design](https://gitlab.com/gitlab-org/gitlab-design)
-repository and maintained by GitLab UX designers.
+All design files are stored in the [gitlab-design](https://gitlab.com/gitlab-org/gitlab-design)
+repository and maintained by GitLab UX designers.
## Navigation
-GitLab's layout contains 2 sections: the left sidebar and the content. The left sidebar contains a static navigation menu.
-This menu will be visible regardless of what page you visit. The left sidebar also contains the GitLab logo
-and the current user's profile picture. The content section contains a header and the content itself.
-The header describes the current GitLab page and what navigation is
-available to user in this area. Depending on the area (project, group, profile setting) the header name and navigation may change. For example when user visits one of the
+GitLab's layout contains 2 sections: the left sidebar and the content. The left sidebar contains a static navigation menu.
+This menu will be visible regardless of what page you visit. The left sidebar also contains the GitLab logo
+and the current user's profile picture. The content section contains a header and the content itself.
+The header describes the current GitLab page and what navigation is
+available to user in this area. Depending on the area (project, group, profile setting) the header name and navigation may change. For example when user visits one of the
project pages the header will contain a project name and navigation for that project. When the user visits a group page it will contain a group name and navigation related to this group.
### Adding new tab to header navigation
-We try to keep the amount of tabs in the header navigation between 5 and 10 so that it fits on a typical laptop screen. We also try not to confuse the user with too many options. Ideally each
-tab should represent separate functionality. Everything related to the issue
-tracker should be under the 'Issues' tab while everything related to the wiki should
+We try to keep the amount of tabs in the header navigation between 5 and 10 so that it fits on a typical laptop screen. We also try not to confuse the user with too many options. Ideally each
+tab should represent separate functionality. Everything related to the issue
+tracker should be under the 'Issues' tab while everything related to the wiki should
be under 'Wiki' tab and so on and so forth.
+When adding a new tab to the header don't use more than 2 words for text in the link.
+We want to keep links short and easy to remember and fit all of them in the small screen.
-## Mobile screen size
+## Mobile screen size
-We want GitLab to work well on small mobile screens as well. Size limitations make it is impossible to fit everything on a mobile screen. In this case it is OK to hide
-part of the UI for smaller resolutions in favor of a better user experience.
+We want GitLab to work well on small mobile screens as well. Size limitations make it is impossible to fit everything on a mobile screen. In this case it is OK to hide
+part of the UI for smaller resolutions in favor of a better user experience.
However core functionality like browsing files, creating issues, writing comments, should
be available on all resolutions.
## Icons
-* `trash` icon for button or link that does destructive action like removing
+* `trash` icon for button or link that does destructive action like removing
information from database or file system
* `x` icon for closing/hiding UI element. For example close modal window
* `pencil` icon for edit button or link
@@ -50,7 +52,14 @@ information from database or file system
* Button should contain icon or text. Exceptions should be approved by UX designer.
* Use red button for destructive actions (not revertable). For example removing issue.
-* Use green or blue button for primary action. Primary button should be only one.
-Do not use both green and blue button in one form.
-* For all other cases use default white button
+* Use green or blue button for primary action. Primary button should be only one.
+Do not use both green and blue button in one form.
+* For all other cases use default white button.
+* Text button should have only first word capitalized. So should be "Create issue" instead of "Create Issue"
+## Counts
+
+* Always use the [`number_with_delimiter`][number_with_delimiter] helper to
+ display counts in the UI.
+
+[number_with_delimiter]: http://api.rubyonrails.org/classes/ActionView/Helpers/NumberHelper.html#method-i-number_with_delimiter
diff --git a/doc/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
index 9d93b217a59..e60992c4c6a 100644
--- a/doc/gitlab-basics/basicsimages/add_new_merge_request.png
+++ b/doc/gitlab-basics/basicsimages/add_new_merge_request.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/add_sshkey.png b/doc/gitlab-basics/basicsimages/add_sshkey.png
index 2dede97aa40..89c86018629 100644
--- a/doc/gitlab-basics/basicsimages/add_sshkey.png
+++ b/doc/gitlab-basics/basicsimages/add_sshkey.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/branch_info.png b/doc/gitlab-basics/basicsimages/branch_info.png
index c5e38b552a5..2264f3c5bf2 100644
--- a/doc/gitlab-basics/basicsimages/branch_info.png
+++ b/doc/gitlab-basics/basicsimages/branch_info.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/branch_name.png b/doc/gitlab-basics/basicsimages/branch_name.png
index 06e77f5eea9..75fe8313611 100644
--- a/doc/gitlab-basics/basicsimages/branch_name.png
+++ b/doc/gitlab-basics/basicsimages/branch_name.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/branches.png b/doc/gitlab-basics/basicsimages/branches.png
index c18fa83b968..8621bc05776 100644
--- a/doc/gitlab-basics/basicsimages/branches.png
+++ b/doc/gitlab-basics/basicsimages/branches.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/button-create-mr.png b/doc/gitlab-basics/basicsimages/button-create-mr.png
index 457af459bb9..b52ab148839 100644
--- a/doc/gitlab-basics/basicsimages/button-create-mr.png
+++ b/doc/gitlab-basics/basicsimages/button-create-mr.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/click-on-new-group.png b/doc/gitlab-basics/basicsimages/click-on-new-group.png
index 94b6d5756d3..6450deec6fc 100644
--- a/doc/gitlab-basics/basicsimages/click-on-new-group.png
+++ b/doc/gitlab-basics/basicsimages/click-on-new-group.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/commit_changes.png b/doc/gitlab-basics/basicsimages/commit_changes.png
index 81588336f37..a88809c5a3f 100644
--- a/doc/gitlab-basics/basicsimages/commit_changes.png
+++ b/doc/gitlab-basics/basicsimages/commit_changes.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/commit_message.png b/doc/gitlab-basics/basicsimages/commit_message.png
index 0df2c32653c..4abe4517f98 100644
--- a/doc/gitlab-basics/basicsimages/commit_message.png
+++ b/doc/gitlab-basics/basicsimages/commit_message.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/commits.png b/doc/gitlab-basics/basicsimages/commits.png
index 7e606539077..2bfcaf75f01 100644
--- a/doc/gitlab-basics/basicsimages/commits.png
+++ b/doc/gitlab-basics/basicsimages/commits.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/compare_branches.png b/doc/gitlab-basics/basicsimages/compare_branches.png
index 7eebaed9075..8a18453dd05 100644
--- a/doc/gitlab-basics/basicsimages/compare_branches.png
+++ b/doc/gitlab-basics/basicsimages/compare_branches.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/create_file.png b/doc/gitlab-basics/basicsimages/create_file.png
index 688e355cca2..5ebe1b227dd 100644
--- a/doc/gitlab-basics/basicsimages/create_file.png
+++ b/doc/gitlab-basics/basicsimages/create_file.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/create_group.png b/doc/gitlab-basics/basicsimages/create_group.png
index 57da898abdc..7ecc3baa990 100644
--- a/doc/gitlab-basics/basicsimages/create_group.png
+++ b/doc/gitlab-basics/basicsimages/create_group.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/edit_file.png b/doc/gitlab-basics/basicsimages/edit_file.png
index afa68760108..9d3e817d036 100644
--- a/doc/gitlab-basics/basicsimages/edit_file.png
+++ b/doc/gitlab-basics/basicsimages/edit_file.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/file_located.png b/doc/gitlab-basics/basicsimages/file_located.png
index 1def489d16b..e357cb5c6ab 100644
--- a/doc/gitlab-basics/basicsimages/file_located.png
+++ b/doc/gitlab-basics/basicsimages/file_located.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/file_name.png b/doc/gitlab-basics/basicsimages/file_name.png
index 9ac2f1c355f..01639c77d0d 100644
--- a/doc/gitlab-basics/basicsimages/file_name.png
+++ b/doc/gitlab-basics/basicsimages/file_name.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/find_file.png b/doc/gitlab-basics/basicsimages/find_file.png
index 98639149a39..6f26d26ae18 100644
--- a/doc/gitlab-basics/basicsimages/find_file.png
+++ b/doc/gitlab-basics/basicsimages/find_file.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/find_group.png b/doc/gitlab-basics/basicsimages/find_group.png
index 5ac33c7e953..1211510aae9 100644
--- a/doc/gitlab-basics/basicsimages/find_group.png
+++ b/doc/gitlab-basics/basicsimages/find_group.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/fork.png b/doc/gitlab-basics/basicsimages/fork.png
index b1f94938613..13ff8345616 100644
--- a/doc/gitlab-basics/basicsimages/fork.png
+++ b/doc/gitlab-basics/basicsimages/fork.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/group_info.png b/doc/gitlab-basics/basicsimages/group_info.png
index e78d84e4d80..2507d6c295b 100644
--- a/doc/gitlab-basics/basicsimages/group_info.png
+++ b/doc/gitlab-basics/basicsimages/group_info.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/groups.png b/doc/gitlab-basics/basicsimages/groups.png
index b8104343afa..ef3dca60cc8 100644
--- a/doc/gitlab-basics/basicsimages/groups.png
+++ b/doc/gitlab-basics/basicsimages/groups.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/https.png b/doc/gitlab-basics/basicsimages/https.png
index 2a31b4cf751..e74dbc13f9a 100644
--- a/doc/gitlab-basics/basicsimages/https.png
+++ b/doc/gitlab-basics/basicsimages/https.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/image_file.png b/doc/gitlab-basics/basicsimages/image_file.png
index 1061d9c5082..7f304b8e1f2 100644
--- a/doc/gitlab-basics/basicsimages/image_file.png
+++ b/doc/gitlab-basics/basicsimages/image_file.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/issue_title.png b/doc/gitlab-basics/basicsimages/issue_title.png
index 7b69c705392..60a6f7973be 100644
--- a/doc/gitlab-basics/basicsimages/issue_title.png
+++ b/doc/gitlab-basics/basicsimages/issue_title.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/issues.png b/doc/gitlab-basics/basicsimages/issues.png
index 9354d05319e..14e9cdb64e1 100644
--- a/doc/gitlab-basics/basicsimages/issues.png
+++ b/doc/gitlab-basics/basicsimages/issues.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/key.png b/doc/gitlab-basics/basicsimages/key.png
index 321805cda98..04400173ce8 100644
--- a/doc/gitlab-basics/basicsimages/key.png
+++ b/doc/gitlab-basics/basicsimages/key.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/merge_requests.png b/doc/gitlab-basics/basicsimages/merge_requests.png
index 7601d40de47..570164df18b 100644
--- a/doc/gitlab-basics/basicsimages/merge_requests.png
+++ b/doc/gitlab-basics/basicsimages/merge_requests.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/new_merge_request.png b/doc/gitlab-basics/basicsimages/new_merge_request.png
index 9120d2b1ab1..842f5ebed74 100644
--- a/doc/gitlab-basics/basicsimages/new_merge_request.png
+++ b/doc/gitlab-basics/basicsimages/new_merge_request.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/new_project.png b/doc/gitlab-basics/basicsimages/new_project.png
index ac255270a66..421e8bc247b 100644
--- a/doc/gitlab-basics/basicsimages/new_project.png
+++ b/doc/gitlab-basics/basicsimages/new_project.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/newbranch.png b/doc/gitlab-basics/basicsimages/newbranch.png
index da1a6b604ea..d5fcf33c4ea 100644
--- a/doc/gitlab-basics/basicsimages/newbranch.png
+++ b/doc/gitlab-basics/basicsimages/newbranch.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/paste_sshkey.png b/doc/gitlab-basics/basicsimages/paste_sshkey.png
index 9880ddfead1..578ebee4440 100644
--- a/doc/gitlab-basics/basicsimages/paste_sshkey.png
+++ b/doc/gitlab-basics/basicsimages/paste_sshkey.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/profile_settings.png b/doc/gitlab-basics/basicsimages/profile_settings.png
index 5f2e7a7e10c..cb3f79f1879 100644
--- a/doc/gitlab-basics/basicsimages/profile_settings.png
+++ b/doc/gitlab-basics/basicsimages/profile_settings.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/project_info.png b/doc/gitlab-basics/basicsimages/project_info.png
index 6c06ff351fa..e1adb8d48c2 100644
--- a/doc/gitlab-basics/basicsimages/project_info.png
+++ b/doc/gitlab-basics/basicsimages/project_info.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/public_file_link.png b/doc/gitlab-basics/basicsimages/public_file_link.png
index 1a60a3d880a..f60df6807f4 100644
--- a/doc/gitlab-basics/basicsimages/public_file_link.png
+++ b/doc/gitlab-basics/basicsimages/public_file_link.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/select-group.png b/doc/gitlab-basics/basicsimages/select-group.png
index d02c2255ff2..33b978dd899 100644
--- a/doc/gitlab-basics/basicsimages/select-group.png
+++ b/doc/gitlab-basics/basicsimages/select-group.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/select-group2.png b/doc/gitlab-basics/basicsimages/select-group2.png
index fd40bce499b..aee22c638db 100644
--- a/doc/gitlab-basics/basicsimages/select-group2.png
+++ b/doc/gitlab-basics/basicsimages/select-group2.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/select_branch.png b/doc/gitlab-basics/basicsimages/select_branch.png
index 3475b2df576..f72a3ffb57f 100644
--- a/doc/gitlab-basics/basicsimages/select_branch.png
+++ b/doc/gitlab-basics/basicsimages/select_branch.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/select_project.png b/doc/gitlab-basics/basicsimages/select_project.png
index 6d5aa439124..3bb832ea8d0 100644
--- a/doc/gitlab-basics/basicsimages/select_project.png
+++ b/doc/gitlab-basics/basicsimages/select_project.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/settings.png b/doc/gitlab-basics/basicsimages/settings.png
index 9bf9c5a0d39..78637013d9b 100644
--- a/doc/gitlab-basics/basicsimages/settings.png
+++ b/doc/gitlab-basics/basicsimages/settings.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/shh_keys.png b/doc/gitlab-basics/basicsimages/shh_keys.png
index d7ef4dafe77..c87f11a9d3d 100644
--- a/doc/gitlab-basics/basicsimages/shh_keys.png
+++ b/doc/gitlab-basics/basicsimages/shh_keys.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/submit_new_issue.png b/doc/gitlab-basics/basicsimages/submit_new_issue.png
index 18944417085..78b854c8903 100644
--- a/doc/gitlab-basics/basicsimages/submit_new_issue.png
+++ b/doc/gitlab-basics/basicsimages/submit_new_issue.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/title_description_mr.png b/doc/gitlab-basics/basicsimages/title_description_mr.png
index e08eb628414..c31d61ec336 100644
--- a/doc/gitlab-basics/basicsimages/title_description_mr.png
+++ b/doc/gitlab-basics/basicsimages/title_description_mr.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/white_space.png b/doc/gitlab-basics/basicsimages/white_space.png
index 6363a09360e..eaa969bdcf4 100644
--- a/doc/gitlab-basics/basicsimages/white_space.png
+++ b/doc/gitlab-basics/basicsimages/white_space.png
Binary files differ
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/install/requirements.md b/doc/install/requirements.md
index 09c6211b3ab..a65ac8a5f79 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -52,7 +52,7 @@ If you have enough RAM memory and a recent CPU the speed of GitLab is mainly lim
### CPU
-- 1 core works supports up to 100 users but the application can be a bit slower due to having all workers and background jobs running on the same core
+- 1 core supports up to 100 users but the application can be a bit slower due to having all workers and background jobs running on the same core
- **2 cores** is the **recommended** number of cores and supports up to 500 users
- 4 cores supports up to 2,000 users
- 8 cores supports up to 5,000 users
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
index ccdd3adb1c5..c2aa97b132e 100644
--- a/doc/integration/img/akismet_settings.png
+++ b/doc/integration/img/akismet_settings.png
Binary files differ
diff --git a/doc/integration/img/enabled-oauth-sign-in-sources.png b/doc/integration/img/enabled-oauth-sign-in-sources.png
index 95f8bbdcd24..b23d6dcc595 100644
--- a/doc/integration/img/enabled-oauth-sign-in-sources.png
+++ b/doc/integration/img/enabled-oauth-sign-in-sources.png
Binary files differ
diff --git a/doc/integration/img/facebook_api_keys.png b/doc/integration/img/facebook_api_keys.png
index d6c44ac0f11..995845d5a69 100644
--- a/doc/integration/img/facebook_api_keys.png
+++ b/doc/integration/img/facebook_api_keys.png
Binary files differ
diff --git a/doc/integration/img/facebook_app_settings.png b/doc/integration/img/facebook_app_settings.png
index 30dd21e198a..1cd586ecd7c 100644
--- a/doc/integration/img/facebook_app_settings.png
+++ b/doc/integration/img/facebook_app_settings.png
Binary files differ
diff --git a/doc/integration/img/facebook_website_url.png b/doc/integration/img/facebook_website_url.png
index dc3088bb2fa..10e1bd5d5a6 100644
--- a/doc/integration/img/facebook_website_url.png
+++ b/doc/integration/img/facebook_website_url.png
Binary files differ
diff --git a/doc/integration/img/github_app.png b/doc/integration/img/github_app.png
index d890345ced9..de31242679a 100644
--- a/doc/integration/img/github_app.png
+++ b/doc/integration/img/github_app.png
Binary files differ
diff --git a/doc/integration/img/gitlab_app.png b/doc/integration/img/gitlab_app.png
index 3f9391a821b..065316fd3c7 100644
--- a/doc/integration/img/gitlab_app.png
+++ b/doc/integration/img/gitlab_app.png
Binary files differ
diff --git a/doc/integration/img/gmail_action_buttons_for_gitlab.png b/doc/integration/img/gmail_action_buttons_for_gitlab.png
index b08f54d137b..a6704139091 100644
--- a/doc/integration/img/gmail_action_buttons_for_gitlab.png
+++ b/doc/integration/img/gmail_action_buttons_for_gitlab.png
Binary files differ
diff --git a/doc/integration/img/google_app.png b/doc/integration/img/google_app.png
index 5a62ad35009..08f7f714553 100644
--- a/doc/integration/img/google_app.png
+++ b/doc/integration/img/google_app.png
Binary files differ
diff --git a/doc/integration/img/oauth_provider_admin_application.png b/doc/integration/img/oauth_provider_admin_application.png
index a2d8e14c120..fc5f7596fcc 100644
--- a/doc/integration/img/oauth_provider_admin_application.png
+++ b/doc/integration/img/oauth_provider_admin_application.png
Binary files differ
diff --git a/doc/integration/img/oauth_provider_application_form.png b/doc/integration/img/oauth_provider_application_form.png
index 3a676b22393..606ab3e3467 100644
--- a/doc/integration/img/oauth_provider_application_form.png
+++ b/doc/integration/img/oauth_provider_application_form.png
Binary files differ
diff --git a/doc/integration/img/oauth_provider_application_id_secret.png b/doc/integration/img/oauth_provider_application_id_secret.png
index 6d68df001af..cbedcef8376 100644
--- a/doc/integration/img/oauth_provider_application_id_secret.png
+++ b/doc/integration/img/oauth_provider_application_id_secret.png
Binary files differ
diff --git a/doc/integration/img/oauth_provider_authorized_application.png b/doc/integration/img/oauth_provider_authorized_application.png
index efc3b807d71..6a2ea09073c 100644
--- a/doc/integration/img/oauth_provider_authorized_application.png
+++ b/doc/integration/img/oauth_provider_authorized_application.png
Binary files differ
diff --git a/doc/integration/img/oauth_provider_user_wide_applications.png b/doc/integration/img/oauth_provider_user_wide_applications.png
index 45ad8a6d468..0c7b095a2dd 100644
--- a/doc/integration/img/oauth_provider_user_wide_applications.png
+++ b/doc/integration/img/oauth_provider_user_wide_applications.png
Binary files differ
diff --git a/doc/integration/img/twitter_app_api_keys.png b/doc/integration/img/twitter_app_api_keys.png
index 1076337172a..15b29ac7d16 100644
--- a/doc/integration/img/twitter_app_api_keys.png
+++ b/doc/integration/img/twitter_app_api_keys.png
Binary files differ
diff --git a/doc/integration/img/twitter_app_details.png b/doc/integration/img/twitter_app_details.png
index b95e8af8a74..323112a88bb 100644
--- a/doc/integration/img/twitter_app_details.png
+++ b/doc/integration/img/twitter_app_details.png
Binary files differ
diff --git a/doc/integration/oauth_provider.md b/doc/integration/oauth_provider.md
index 5f8bb57365c..0c53584d201 100644
--- a/doc/integration/oauth_provider.md
+++ b/doc/integration/oauth_provider.md
@@ -28,7 +28,8 @@ GitLab supports two ways of adding a new OAuth2 application to an instance. You
can either add an application as a regular user or add it in the admin area.
What this means is that GitLab can actually have instance-wide and a user-wide
applications. There is no difference between them except for the different
-permission levels they are set (user/admin).
+permission levels they are set (user/admin). The default callback URL is
+`http://your-gitlab.example.com/users/auth/gitlab/callback`
## Adding an application through the profile
diff --git a/doc/integration/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/saml.md b/doc/integration/saml.md
index 8a7205caaa4..f3b2a288776 100644
--- a/doc/integration/saml.md
+++ b/doc/integration/saml.md
@@ -138,7 +138,7 @@ This setting is only available on GitLab 8.7 and above.
SAML login includes support for external groups. You can define in the SAML
settings which groups, to which your users belong in your IdP, you wish to be
-marked as [external](../permissions/permissions.md).
+marked as [external](../user/permissions.md).
### Requirements
@@ -306,4 +306,4 @@ For this you need take the following into account:
validators are optional
Make sure that one of the above described scenarios is valid, or the requests will
-fail with one of the mentioned errors. \ No newline at end of file
+fail with one of the mentioned errors.
diff --git a/doc/integration/shibboleth.md b/doc/integration/shibboleth.md
index b6b2d4e5e88..5210ce0de9a 100644
--- a/doc/integration/shibboleth.md
+++ b/doc/integration/shibboleth.md
@@ -2,7 +2,7 @@
This documentation is for enabling shibboleth with omnibus-gitlab package.
-In order to enable Shibboleth support in gitlab we need to use Apache instead of Nginx (It may be possible to use Nginx, however I did not found way to easily configure Nginx that is bundled in omnibus-gitlab package). Apache uses mod_shib2 module for shibboleth authentication and can pass attributes as headers to omniauth-shibboleth provider.
+In order to enable Shibboleth support in gitlab we need to use Apache instead of Nginx (It may be possible to use Nginx, however this is difficult to configure using the bundled NIGNX provided in the omnibus-gitlab package). Apache uses mod_shib2 module for shibboleth authentication and can pass attributes as headers to omniauth-shibboleth provider.
To enable the Shibboleth OmniAuth provider you must:
diff --git a/doc/intro/README.md b/doc/intro/README.md
index 382d10aaf40..1850031eb26 100644
--- a/doc/intro/README.md
+++ b/doc/intro/README.md
@@ -12,7 +12,7 @@ Create projects and groups.
Create issues, labels, milestones, cast your vote, and review issues.
- [Create a new issue](../gitlab-basics/create-issue.md)
-- [Assign labels to issues](../workflow/labels.md)
+- [Assign labels to issues](../user/project/labels.md)
- [Use milestones as an overview of your project's tracker](../workflow/milestones.md)
- [Use voting to express your like/dislike to issues and merge requests](../workflow/award_emoji.md)
diff --git a/doc/markdown/img/logo.png b/doc/markdown/img/logo.png
index 7da5f23ed9b..05c8b0d0ccf 100644
--- a/doc/markdown/img/logo.png
+++ b/doc/markdown/img/logo.png
Binary files differ
diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md
index 236eb7b12c4..fb2dd582754 100644
--- a/doc/markdown/markdown.md
+++ b/doc/markdown/markdown.md
@@ -7,11 +7,12 @@
* [Newlines](#newlines)
* [Multiple underscores in words](#multiple-underscores-in-words)
* [URL auto-linking](#url-auto-linking)
+* [Multiline Blockquote](#multiline-blockquote)
* [Code and Syntax Highlighting](#code-and-syntax-highlighting)
* [Inline Diff](#inline-diff)
* [Emoji](#emoji)
* [Special GitLab references](#special-gitlab-references)
-* [Task lists](#task-lists)
+* [Task Lists](#task-lists)
**[Standard Markdown](#standard-markdown)**
@@ -89,6 +90,37 @@ GFM will autolink almost any URL you copy and paste into your text.
* irc://irc.freenode.net/gitlab
* http://localhost:3000
+## Multiline Blockquote
+
+On top of standard Markdown [blockquotes](#blockquotes), which require prepending `>` to quoted lines,
+GFM supports multiline blockquotes fenced by <code>>>></code>.
+
+```no-highlight
+>>>
+If you paste a message from somewhere else
+
+that
+
+spans
+
+multiple lines,
+
+you can quote that without having to manually prepend `>` to every line!
+>>>
+```
+
+>>>
+If you paste a message from somewhere else
+
+that
+
+spans
+
+multiple lines,
+
+you can quote that without having to manually prepend `>` to every line!
+>>>
+
## Code and Syntax Highlighting
_GitLab uses the [Rouge Ruby library][rouge] for syntax highlighting. For a
diff --git a/doc/monitoring/img/health_check_token.png b/doc/monitoring/img/health_check_token.png
index 2daf8606b00..2d7c82a65a8 100644
--- a/doc/monitoring/img/health_check_token.png
+++ b/doc/monitoring/img/health_check_token.png
Binary files differ
diff --git a/doc/monitoring/performance/grafana_configuration.md b/doc/monitoring/performance/grafana_configuration.md
index 168bd85c26a..7947b0fedc4 100644
--- a/doc/monitoring/performance/grafana_configuration.md
+++ b/doc/monitoring/performance/grafana_configuration.md
@@ -44,70 +44,32 @@ on a separate server)
## Apply retention policies and create continuous queries
-If you intend to import the GitLab provided Grafana dashboards, you will need
-to copy and run a set of queries against InfluxDB to create the needed data
-sets.
+If you intend to import the GitLab provided Grafana dashboards, you will need to
+set up the right retention policies and continuous queries. The easiest way of
+doing this is by using the [influxdb-management](https://gitlab.com/gitlab-org/influxdb-management)
+repository.
-On the InfluxDB server, run the following command, substituting your InfluxDB
-user and password:
+To use this repository you must first clone it:
-```bash
-influxdb --username admin -password super_secret
+```
+git clone https://gitlab.com/gitlab-org/influxdb-management.git
+cd influxdb-management
```
-This will drop you in to an InfluxDB interactive session. Copy the entire
-contents below and paste it in to the interactive session:
+Next you must install the required dependencies:
```
-CREATE RETENTION POLICY default ON gitlab DURATION 1h REPLICATION 1 DEFAULT
-CREATE RETENTION POLICY downsampled ON gitlab DURATION 7d REPLICATION 1
-CREATE CONTINUOUS QUERY grape_git_timings_per_action ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.grape_git_timings_per_action FROM gitlab."default".rails_method_calls WHERE (action !~ /.+/ OR action =~ /^Grape#/) AND method =~ /^(Rugged|Gitlab::Git)/ GROUP BY time(1m), action END;
-CREATE CONTINUOUS QUERY grape_markdown_render_timings_overall ON gitlab BEGIN SELECT mean(banzai_cached_render_real_time) AS cached_real_mean, percentile(banzai_cached_render_real_time, 95) AS cached_real_95th, percentile(banzai_cached_render_real_time, 99) AS cached_real_99th, mean(banzai_cached_render_cpu_time) AS cached_cpu_mean, percentile(banzai_cached_render_cpu_time, 95) AS cached_cpu_95th, percentile(banzai_cached_render_cpu_time, 99) AS cached_cpu_99th, sum(banzai_cached_render_call_count) AS cached_call_count, mean(banzai_cacheless_render_real_time) AS cacheless_real_mean, percentile(banzai_cacheless_render_real_time, 95) AS cacheless_real_95th, percentile(banzai_cacheless_render_real_time, 99) AS cacheless_real_99th, mean(banzai_cacheless_render_cpu_time) AS cacheless_cpu_mean, percentile(banzai_cacheless_render_cpu_time, 95) AS cacheless_cpu_95th, percentile(banzai_cacheless_render_cpu_time, 99) AS cacheless_cpu_99th, sum(banzai_cacheless_render_call_count) AS cacheless_call_count INTO gitlab.downsampled.grape_markdown_render_timings_overall FROM gitlab."default".rails_transactions WHERE (action !~ /.+/ OR action =~ /^Grape#/) AND (banzai_cached_render_call_count > 0 OR banzai_cacheless_render_call_count > 0) GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY grape_markdown_render_timings_per_action ON gitlab BEGIN SELECT mean(banzai_cached_render_real_time) AS cached_real_mean, percentile(banzai_cached_render_real_time, 95) AS cached_real_95th, percentile(banzai_cached_render_real_time, 99) AS cached_real_99th, mean(banzai_cached_render_cpu_time) AS cached_cpu_mean, percentile(banzai_cached_render_cpu_time, 95) AS cached_cpu_95th, percentile(banzai_cached_render_cpu_time, 99) AS cached_cpu_99th, sum(banzai_cached_render_call_count) AS cached_call_count, mean(banzai_cacheless_render_real_time) AS cacheless_real_mean, percentile(banzai_cacheless_render_real_time, 95) AS cacheless_real_95th, percentile(banzai_cacheless_render_real_time, 99) AS cacheless_real_99th, mean(banzai_cacheless_render_cpu_time) AS cacheless_cpu_mean, percentile(banzai_cacheless_render_cpu_time, 95) AS cacheless_cpu_95th, percentile(banzai_cacheless_render_cpu_time, 99) AS cacheless_cpu_99th, sum(banzai_cacheless_render_call_count) AS cacheless_call_count INTO gitlab.downsampled.grape_markdown_render_timings_per_action FROM gitlab."default".rails_transactions WHERE (action !~ /.+/ OR action =~ /^Grape#/) AND (banzai_cached_render_call_count > 0 OR banzai_cacheless_render_call_count > 0) GROUP BY time(1m), action END;
-CREATE CONTINUOUS QUERY grape_markdown_timings_overall ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.grape_markdown_timings_overall FROM gitlab."default".rails_method_calls WHERE (action !~ /.+/ OR action =~ /^Grape#/) AND method =~ /^Banzai/ GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY grape_method_call_timings_per_action_and_method ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.grape_method_call_timings_per_action_and_method FROM gitlab."default".rails_method_calls WHERE action !~ /.+/ OR action =~ /^Grape#/ GROUP BY time(1m), method, action END;
-CREATE CONTINUOUS QUERY grape_method_call_timings_per_method ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.grape_method_call_timings_per_method FROM gitlab."default".rails_method_calls WHERE action !~ /.+/ OR action =~ /^Grape#/ GROUP BY time(1m), method END;
-CREATE CONTINUOUS QUERY grape_transaction_counts_overall ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.downsampled.grape_transaction_counts_overall FROM gitlab."default".rails_transactions WHERE action !~ /.+/ OR action =~ /^Grape#/ GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY grape_transaction_counts_per_action ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.downsampled.grape_transaction_counts_per_action FROM gitlab."default".rails_transactions WHERE action !~ /.+/ OR action =~ /^Grape#/ GROUP BY time(1m), action END;
-CREATE CONTINUOUS QUERY grape_transaction_timings_overall ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(sql_duration, 95) AS sql_duration_95th, percentile(sql_duration, 99) AS sql_duration_99th, mean(view_duration) AS view_duration_mean, percentile(view_duration, 95) AS view_duration_95th, percentile(view_duration, 99) AS view_duration_99th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_duration) AS cache_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(method_duration) AS method_duration_mean, percentile(method_duration, 99) AS method_duration_99th, percentile(method_duration, 95) AS method_duration_95th INTO gitlab.downsampled.grape_transaction_timings_overall FROM gitlab."default".rails_transactions WHERE action !~ /.+/ OR action =~ /^Grape#/ GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY grape_transaction_timings_per_action ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(sql_duration, 95) AS sql_duration_95th, percentile(sql_duration, 99) AS sql_duration_99th, mean(view_duration) AS view_duration_mean, percentile(view_duration, 95) AS view_duration_95th, percentile(view_duration, 99) AS view_duration_99th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_duration) AS cache_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(method_duration) AS method_duration_mean, percentile(method_duration, 99) AS method_duration_99th, percentile(method_duration, 95) AS method_duration_95th INTO gitlab.downsampled.grape_transaction_timings_per_action FROM gitlab."default".rails_transactions WHERE action !~ /.+/ OR action =~ /^Grape#/ GROUP BY time(1m), action END;
-CREATE CONTINUOUS QUERY rails_file_descriptor_counts ON gitlab BEGIN SELECT sum(value) AS count INTO gitlab.downsampled.rails_file_descriptor_counts FROM gitlab."default".rails_file_descriptors GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY rails_gc_counts ON gitlab BEGIN SELECT sum(count) AS total, sum(minor_gc_count) AS minor, sum(major_gc_count) AS major INTO gitlab.downsampled.rails_gc_counts FROM gitlab."default".rails_gc_statistics GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY rails_gc_timings ON gitlab BEGIN SELECT mean(total_time) AS duration_mean, percentile(total_time, 95) AS duration_95th, percentile(total_time, 99) AS duration_99th INTO gitlab.downsampled.rails_gc_timings FROM gitlab."default".rails_gc_statistics GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY rails_git_timings_per_action ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.rails_git_timings_per_action FROM gitlab."default".rails_method_calls WHERE (action =~ /.+/ AND action !~ /^Grape#/) AND method =~ /^(Rugged|Gitlab::Git)/ GROUP BY time(1m), action END;
-CREATE CONTINUOUS QUERY rails_markdown_render_timings_overall ON gitlab BEGIN SELECT mean(banzai_cached_render_real_time) AS cached_real_mean, percentile(banzai_cached_render_real_time, 95) AS cached_real_95th, percentile(banzai_cached_render_real_time, 99) AS cached_real_99th, mean(banzai_cached_render_cpu_time) AS cached_cpu_mean, percentile(banzai_cached_render_cpu_time, 95) AS cached_cpu_95th, percentile(banzai_cached_render_cpu_time, 99) AS cached_cpu_99th, sum(banzai_cached_render_call_count) AS cached_call_count, mean(banzai_cacheless_render_real_time) AS cacheless_real_mean, percentile(banzai_cacheless_render_real_time, 95) AS cacheless_real_95th, percentile(banzai_cacheless_render_real_time, 99) AS cacheless_real_99th, mean(banzai_cacheless_render_cpu_time) AS cacheless_cpu_mean, percentile(banzai_cacheless_render_cpu_time, 95) AS cacheless_cpu_95th, percentile(banzai_cacheless_render_cpu_time, 99) AS cacheless_cpu_99th, sum(banzai_cacheless_render_call_count) AS cacheless_call_count INTO gitlab.downsampled.rails_markdown_render_timings_overall FROM gitlab."default".rails_transactions WHERE (action =~ /.+/ AND action !~ /^Grape#/) AND (banzai_cached_render_call_count > 0 OR banzai_cacheless_render_call_count > 0) GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY rails_markdown_render_timings_per_action ON gitlab BEGIN SELECT mean(banzai_cached_render_real_time) AS cached_real_mean, percentile(banzai_cached_render_real_time, 95) AS cached_real_95th, percentile(banzai_cached_render_real_time, 99) AS cached_real_99th, mean(banzai_cached_render_cpu_time) AS cached_cpu_mean, percentile(banzai_cached_render_cpu_time, 95) AS cached_cpu_95th, percentile(banzai_cached_render_cpu_time, 99) AS cached_cpu_99th, sum(banzai_cached_render_call_count) AS cached_call_count, mean(banzai_cacheless_render_real_time) AS cacheless_real_mean, percentile(banzai_cacheless_render_real_time, 95) AS cacheless_real_95th, percentile(banzai_cacheless_render_real_time, 99) AS cacheless_real_99th, mean(banzai_cacheless_render_cpu_time) AS cacheless_cpu_mean, percentile(banzai_cacheless_render_cpu_time, 95) AS cacheless_cpu_95th, percentile(banzai_cacheless_render_cpu_time, 99) AS cacheless_cpu_99th, sum(banzai_cacheless_render_call_count) AS cacheless_call_count INTO gitlab.downsampled.rails_markdown_render_timings_per_action FROM gitlab."default".rails_transactions WHERE (action =~ /.+/ AND action !~ /^Grape#/) AND (banzai_cached_render_call_count > 0 OR banzai_cacheless_render_call_count > 0) GROUP BY time(1m), action END;
-CREATE CONTINUOUS QUERY rails_markdown_timings_overall ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.rails_markdown_timings_overall FROM gitlab."default".rails_method_calls WHERE (action =~ /.+/ AND action !~ /^Grape#/) AND method =~ /^Banzai/ GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY rails_memory_usage_overall ON gitlab BEGIN SELECT mean(value) AS memory_mean, percentile(value, 95) AS memory_95th, percentile(value, 99) AS memory_99th INTO gitlab.downsampled.rails_memory_usage_overall FROM gitlab."default".rails_memory_usage GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY rails_method_call_timings_per_action_and_method ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.rails_method_call_timings_per_action_and_method FROM gitlab."default".rails_method_calls WHERE action =~ /.+/ AND action !~ /^Grape#/ GROUP BY time(1m), method, action END;
-CREATE CONTINUOUS QUERY rails_method_call_timings_per_method ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.rails_method_call_timings_per_method FROM gitlab."default".rails_method_calls WHERE action =~ /.+/ AND action !~ /^Grape#/ GROUP BY time(1m), method END;
-CREATE CONTINUOUS QUERY rails_object_counts_overall ON gitlab BEGIN SELECT sum(count) AS count INTO gitlab.downsampled.rails_object_counts_overall FROM gitlab."default".rails_object_counts GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY rails_object_counts_per_type ON gitlab BEGIN SELECT sum(count) AS count INTO gitlab.downsampled.rails_object_counts_per_type FROM gitlab."default".rails_object_counts GROUP BY time(1m), type END;
-CREATE CONTINUOUS QUERY rails_transaction_counts_overall ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.downsampled.rails_transaction_counts_overall FROM gitlab."default".rails_transactions WHERE action =~ /.+/ AND action !~ /^Grape#/ GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY rails_transaction_counts_per_action ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.downsampled.rails_transaction_counts_per_action FROM gitlab."default".rails_transactions WHERE action =~ /.+/ AND action !~ /^Grape#/ GROUP BY time(1m), action END;
-CREATE CONTINUOUS QUERY rails_transaction_timings_overall ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(sql_duration, 95) AS sql_duration_95th, percentile(sql_duration, 99) AS sql_duration_99th, mean(view_duration) AS view_duration_mean, percentile(view_duration, 95) AS view_duration_95th, percentile(view_duration, 99) AS view_duration_99th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_duration) AS cache_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(method_duration) AS method_duration_mean, percentile(method_duration, 99) AS method_duration_99th, percentile(method_duration, 95) AS method_duration_95th INTO gitlab.downsampled.rails_transaction_timings_overall FROM gitlab."default".rails_transactions WHERE action =~ /.+/ AND action !~ /^Grape#/ GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY rails_transaction_timings_per_action ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(sql_duration, 95) AS sql_duration_95th, percentile(sql_duration, 99) AS sql_duration_99th, mean(view_duration) AS view_duration_mean, percentile(view_duration, 95) AS view_duration_95th, percentile(view_duration, 99) AS view_duration_99th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_duration) AS cache_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(method_duration) AS method_duration_mean, percentile(method_duration, 99) AS method_duration_99th, percentile(method_duration, 95) AS method_duration_95th INTO gitlab.downsampled.rails_transaction_timings_per_action FROM gitlab."default".rails_transactions WHERE action =~ /.+/ AND action !~ /^Grape#/ GROUP BY time(1m), action END;
-CREATE CONTINUOUS QUERY rails_view_timings_per_action_and_view ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.rails_view_timings_per_action_and_view FROM gitlab."default".rails_views WHERE action =~ /.+/ AND action !~ /^Grape#/ GROUP BY time(1m), action, view END;
-CREATE CONTINUOUS QUERY sidekiq_file_descriptor_counts ON gitlab BEGIN SELECT sum(value) AS count INTO gitlab.downsampled.sidekiq_file_descriptor_counts FROM gitlab."default".sidekiq_file_descriptors GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY sidekiq_gc_counts ON gitlab BEGIN SELECT sum(count) AS total, sum(minor_gc_count) AS minor, sum(major_gc_count) AS major INTO gitlab.downsampled.sidekiq_gc_counts FROM gitlab."default".sidekiq_gc_statistics GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY sidekiq_gc_timings ON gitlab BEGIN SELECT mean(total_time) AS duration_mean, percentile(total_time, 95) AS duration_95th, percentile(total_time, 99) AS duration_99th INTO gitlab.downsampled.sidekiq_gc_timings FROM gitlab."default".sidekiq_gc_statistics GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY sidekiq_git_timings_per_action ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.sidekiq_git_timings_per_action FROM gitlab."default".sidekiq_method_calls WHERE method =~ /^(Rugged|Gitlab::Git)/ GROUP BY time(1m), action END;
-CREATE CONTINUOUS QUERY sidekiq_markdown_render_timings_overall ON gitlab BEGIN SELECT mean(banzai_cached_render_real_time) AS cached_real_mean, percentile(banzai_cached_render_real_time, 95) AS cached_real_95th, percentile(banzai_cached_render_real_time, 99) AS cached_real_99th, mean(banzai_cached_render_cpu_time) AS cached_cpu_mean, percentile(banzai_cached_render_cpu_time, 95) AS cached_cpu_95th, percentile(banzai_cached_render_cpu_time, 99) AS cached_cpu_99th, sum(banzai_cached_render_call_count) AS cached_call_count, mean(banzai_cacheless_render_real_time) AS cacheless_real_mean, percentile(banzai_cacheless_render_real_time, 95) AS cacheless_real_95th, percentile(banzai_cacheless_render_real_time, 99) AS cacheless_real_99th, mean(banzai_cacheless_render_cpu_time) AS cacheless_cpu_mean, percentile(banzai_cacheless_render_cpu_time, 95) AS cacheless_cpu_95th, percentile(banzai_cacheless_render_cpu_time, 99) AS cacheless_cpu_99th, sum(banzai_cacheless_render_call_count) AS cacheless_call_count INTO gitlab.downsampled.sidekiq_markdown_render_timings_overall FROM gitlab."default".sidekiq_transactions WHERE (banzai_cached_render_call_count > 0 OR banzai_cacheless_render_call_count > 0) GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY sidekiq_markdown_render_timings_per_action ON gitlab BEGIN SELECT mean(banzai_cached_render_real_time) AS cached_real_mean, percentile(banzai_cached_render_real_time, 95) AS cached_real_95th, percentile(banzai_cached_render_real_time, 99) AS cached_real_99th, mean(banzai_cached_render_cpu_time) AS cached_cpu_mean, percentile(banzai_cached_render_cpu_time, 95) AS cached_cpu_95th, percentile(banzai_cached_render_cpu_time, 99) AS cached_cpu_99th, sum(banzai_cached_render_call_count) AS cached_call_count, mean(banzai_cacheless_render_real_time) AS cacheless_real_mean, percentile(banzai_cacheless_render_real_time, 95) AS cacheless_real_95th, percentile(banzai_cacheless_render_real_time, 99) AS cacheless_real_99th, mean(banzai_cacheless_render_cpu_time) AS cacheless_cpu_mean, percentile(banzai_cacheless_render_cpu_time, 95) AS cacheless_cpu_95th, percentile(banzai_cacheless_render_cpu_time, 99) AS cacheless_cpu_99th, sum(banzai_cacheless_render_call_count) AS cacheless_call_count INTO gitlab.downsampled.sidekiq_markdown_render_timings_per_action FROM gitlab."default".sidekiq_transactions WHERE (banzai_cached_render_call_count > 0 OR banzai_cacheless_render_call_count > 0) GROUP BY time(1m), action END;
-CREATE CONTINUOUS QUERY sidekiq_markdown_timings_overall ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.sidekiq_markdown_timings_overall FROM gitlab."default".sidekiq_method_calls WHERE method =~ /^Banzai/ GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY sidekiq_memory_usage_overall ON gitlab BEGIN SELECT mean(value) AS memory_mean, percentile(value, 95) AS memory_95th, percentile(value, 99) AS memory_99th INTO gitlab.downsampled.sidekiq_memory_usage_overall FROM gitlab."default".sidekiq_memory_usage GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY sidekiq_method_call_timings_per_action_and_method ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.sidekiq_method_call_timings_per_action_and_method FROM gitlab."default".sidekiq_method_calls GROUP BY time(1m), method, action END;
-CREATE CONTINUOUS QUERY sidekiq_method_call_timings_per_method ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.sidekiq_method_call_timings_per_method FROM gitlab."default".sidekiq_method_calls GROUP BY time(1m), method END;
-CREATE CONTINUOUS QUERY sidekiq_object_counts_overall ON gitlab BEGIN SELECT sum(count) AS count INTO gitlab.downsampled.sidekiq_object_counts_overall FROM gitlab."default".sidekiq_object_counts GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY sidekiq_object_counts_per_type ON gitlab BEGIN SELECT sum(count) AS count INTO gitlab.downsampled.sidekiq_object_counts_per_type FROM gitlab."default".sidekiq_object_counts GROUP BY time(1m), type END;
-CREATE CONTINUOUS QUERY sidekiq_transaction_counts_overall ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.downsampled.sidekiq_transaction_counts_overall FROM gitlab."default".sidekiq_transactions GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY sidekiq_transaction_counts_per_action ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.downsampled.sidekiq_transaction_counts_per_action FROM gitlab."default".sidekiq_transactions GROUP BY time(1m), action END;
-CREATE CONTINUOUS QUERY sidekiq_transaction_timings_overall ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(sql_duration, 95) AS sql_duration_95th, percentile(sql_duration, 99) AS sql_duration_99th, mean(view_duration) AS view_duration_mean, percentile(view_duration, 95) AS view_duration_95th, percentile(view_duration, 99) AS view_duration_99th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_duration) AS cache_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(method_duration) AS method_duration_mean, percentile(method_duration, 99) AS method_duration_99th, percentile(method_duration, 95) AS method_duration_95th INTO gitlab.downsampled.sidekiq_transaction_timings_overall FROM gitlab."default".sidekiq_transactions GROUP BY time(1m) END;
-CREATE CONTINUOUS QUERY sidekiq_transaction_timings_per_action ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th, mean(sql_duration) AS sql_duration_mean, percentile(sql_duration, 95) AS sql_duration_95th, percentile(sql_duration, 99) AS sql_duration_99th, mean(view_duration) AS view_duration_mean, percentile(view_duration, 95) AS view_duration_95th, percentile(view_duration, 99) AS view_duration_99th, mean(cache_read_duration) AS cache_read_duration_mean, percentile(cache_read_duration, 99) AS cache_read_duration_99th, percentile(cache_read_duration, 95) AS cache_read_duration_95th, mean(cache_write_duration) AS cache_write_duration_mean, percentile(cache_write_duration, 99) AS cache_write_duration_99th, percentile(cache_write_duration, 95) AS cache_write_duration_95th, mean(cache_delete_duration) AS cache_delete_duration_mean, percentile(cache_delete_duration, 99) AS cache_delete_duration_99th, percentile(cache_delete_duration, 95) AS cache_delete_duration_95th, mean(cache_exists_duration) AS cache_exists_duration_mean, percentile(cache_exists_duration, 99) AS cache_exists_duration_99th, percentile(cache_exists_duration, 95) AS cache_exists_duration_95th, mean(cache_duration) AS cache_duration_mean, percentile(cache_duration, 99) AS cache_duration_99th, percentile(cache_duration, 95) AS cache_duration_95th, mean(method_duration) AS method_duration_mean, percentile(method_duration, 99) AS method_duration_99th, percentile(method_duration, 95) AS method_duration_95th INTO gitlab.downsampled.sidekiq_transaction_timings_per_action FROM gitlab."default".sidekiq_transactions GROUP BY time(1m), action END;
-CREATE CONTINUOUS QUERY sidekiq_view_timings_per_action_and_view ON gitlab BEGIN SELECT mean("duration") AS duration_mean, percentile("duration", 95) AS duration_95th, percentile("duration", 99) AS duration_99th INTO gitlab.downsampled.sidekiq_view_timings_per_action_and_view FROM gitlab."default".sidekiq_views GROUP BY time(1m), action, view END;
-CREATE CONTINUOUS QUERY web_transaction_counts_overall ON gitlab BEGIN SELECT count("duration") AS count INTO gitlab.downsampled.web_transaction_counts_overall FROM gitlab."default".rails_transactions GROUP BY time(1m) END;
+gem install bundler
+bundle install
```
+Now you must configure the repository by first copying `.env.example` to `.env`
+and then editing the `.env` file to contain the correct InfluxDB settings. Once
+configured you can simply run `bundle exec rake` and the InfluxDB database will
+be configured for you.
+
+For more information see the [influxdb-management README](https://gitlab.com/gitlab-org/influxdb-management/blob/master/README.md).
+
## Import Dashboards
You can now import a set of default dashboards that will give you a good
diff --git a/doc/monitoring/performance/img/grafana_dashboard_dropdown.png b/doc/monitoring/performance/img/grafana_dashboard_dropdown.png
index b4448c7a09f..7e34fad71ce 100644
--- a/doc/monitoring/performance/img/grafana_dashboard_dropdown.png
+++ b/doc/monitoring/performance/img/grafana_dashboard_dropdown.png
Binary files differ
diff --git a/doc/monitoring/performance/img/grafana_dashboard_import.png b/doc/monitoring/performance/img/grafana_dashboard_import.png
index 5a2d3c0937a..f97624365c7 100644
--- a/doc/monitoring/performance/img/grafana_dashboard_import.png
+++ b/doc/monitoring/performance/img/grafana_dashboard_import.png
Binary files differ
diff --git a/doc/monitoring/performance/img/grafana_data_source_configuration.png b/doc/monitoring/performance/img/grafana_data_source_configuration.png
index 7e2e111f570..7d50e4c88c2 100644
--- a/doc/monitoring/performance/img/grafana_data_source_configuration.png
+++ b/doc/monitoring/performance/img/grafana_data_source_configuration.png
Binary files differ
diff --git a/doc/monitoring/performance/img/grafana_data_source_empty.png b/doc/monitoring/performance/img/grafana_data_source_empty.png
index 11e27571e64..aa39a53acae 100644
--- a/doc/monitoring/performance/img/grafana_data_source_empty.png
+++ b/doc/monitoring/performance/img/grafana_data_source_empty.png
Binary files differ
diff --git a/doc/monitoring/performance/img/grafana_save_icon.png b/doc/monitoring/performance/img/grafana_save_icon.png
index 3d4265bee8e..c740e33cd1c 100644
--- a/doc/monitoring/performance/img/grafana_save_icon.png
+++ b/doc/monitoring/performance/img/grafana_save_icon.png
Binary files differ
diff --git a/doc/monitoring/performance/img/metrics_gitlab_configuration_settings.png b/doc/monitoring/performance/img/metrics_gitlab_configuration_settings.png
index 14d82b6ac98..e6ed45a0386 100644
--- a/doc/monitoring/performance/img/metrics_gitlab_configuration_settings.png
+++ b/doc/monitoring/performance/img/metrics_gitlab_configuration_settings.png
Binary files differ
diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md
index 963b35de3a0..78d67aeec78 100644
--- a/doc/permissions/permissions.md
+++ b/doc/permissions/permissions.md
@@ -1,101 +1,3 @@
# Permissions
-Users have different abilities depending on the access level they have in a particular group or project.
-
-If a user is both in a project group and in the project itself, the highest permission level is used.
-
-If a user is a GitLab administrator they receive all permissions.
-
-On public and internal projects the Guest role is not enforced.
-All users will be able to create issues, leave comments, and pull or download the project code.
-
-To add or import a user, you can follow the [project users and members
-documentation](../workflow/add-user/add-user.md).
-
-## Project
-
-| Action | Guest | Reporter | Developer | Master | Owner |
-|---------------------------------------|---------|------------|-------------|----------|--------|
-| Create new issue | ✓ | ✓ | ✓ | ✓ | ✓ |
-| Leave comments | ✓ | ✓ | ✓ | ✓ | ✓ |
-| See a list of builds | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
-| See a build log | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
-| Download and browse build artifacts | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
-| Pull project code | | ✓ | ✓ | ✓ | ✓ |
-| Download project | | ✓ | ✓ | ✓ | ✓ |
-| Create code snippets | | ✓ | ✓ | ✓ | ✓ |
-| Manage issue tracker | | ✓ | ✓ | ✓ | ✓ |
-| Manage labels | | ✓ | ✓ | ✓ | ✓ |
-| See a commit status | | ✓ | ✓ | ✓ | ✓ |
-| See a container registry | | ✓ | ✓ | ✓ | ✓ |
-| See environments | | ✓ | ✓ | ✓ | ✓ |
-| Manage merge requests | | | ✓ | ✓ | ✓ |
-| Create new merge request | | | ✓ | ✓ | ✓ |
-| Create new branches | | | ✓ | ✓ | ✓ |
-| Push to non-protected branches | | | ✓ | ✓ | ✓ |
-| Force push to non-protected branches | | | ✓ | ✓ | ✓ |
-| Remove non-protected branches | | | ✓ | ✓ | ✓ |
-| Add tags | | | ✓ | ✓ | ✓ |
-| Write a wiki | | | ✓ | ✓ | ✓ |
-| Cancel and retry builds | | | ✓ | ✓ | ✓ |
-| Create or update commit status | | | ✓ | ✓ | ✓ |
-| Update a container registry | | | ✓ | ✓ | ✓ |
-| Remove a container registry image | | | ✓ | ✓ | ✓ |
-| Create new environments | | | ✓ | ✓ | ✓ |
-| Create new milestones | | | | ✓ | ✓ |
-| Add new team members | | | | ✓ | ✓ |
-| Push to protected branches | | | | ✓ | ✓ |
-| Enable/disable branch protection | | | | ✓ | ✓ |
-| Turn on/off prot. branch push for devs| | | | ✓ | ✓ |
-| Rewrite/remove git tags | | | | ✓ | ✓ |
-| Edit project | | | | ✓ | ✓ |
-| Add deploy keys to project | | | | ✓ | ✓ |
-| Configure project hooks | | | | ✓ | ✓ |
-| Manage runners | | | | ✓ | ✓ |
-| Manage build triggers | | | | ✓ | ✓ |
-| Manage variables | | | | ✓ | ✓ |
-| Delete environments | | | | ✓ | ✓ |
-| Switch visibility level | | | | | ✓ |
-| Transfer project to another namespace | | | | | ✓ |
-| Remove project | | | | | ✓ |
-| Force push to protected branches [^2] | | | | | |
-| Remove protected branches [^2] | | | | | |
-
-[^1]: If **Allow guest to access builds** is enabled in CI settings
-[^2]: Not allowed for Guest, Reporter, Developer, Master, or Owner
-
-## Group
-
-In order for a group to appear as public and be browsable, it must contain at
-least one public project.
-
-Any user can remove themselves from a group, unless they are the last Owner of the group.
-
-| Action | Guest | Reporter | Developer | Master | Owner |
-|-------------------------|-------|----------|-----------|--------|-------|
-| Browse group | ✓ | ✓ | ✓ | ✓ | ✓ |
-| Edit group | | | | | ✓ |
-| Create project in group | | | | ✓ | ✓ |
-| Manage group members | | | | | ✓ |
-| Remove group | | | | | ✓ |
-
-## External Users
-
-In cases where it is desired that a user has access only to some internal or
-private projects, there is the option of creating **External Users**. This
-feature may be useful when for example a contractor is working on a given
-project and should only have access to that project.
-
-External users can only access projects to which they are explicitly granted
-access, thus hiding all other internal or private ones from them. Access can be
-granted by adding the user as member to the project or group.
-
-They will, like usual users, receive a role in the project or group with all
-the abilities that are mentioned in the table above. They cannot however create
-groups or projects, and they have the same access as logged out users in all
-other cases.
-
-An administrator can flag a user as external [through the API](../api/users.md)
-or by checking the checkbox on the admin panel. As an administrator, navigate
-to **Admin > Users** to create a new user or edit an existing one. There, you
-will find the option to flag the user as external.
+This document was moved to [user/permissions.md](../user/permissions.md).
diff --git a/doc/profile/2fa.png b/doc/profile/2fa.png
index bbf415210d5..bb464efa685 100644
--- a/doc/profile/2fa.png
+++ b/doc/profile/2fa.png
Binary files differ
diff --git a/doc/profile/2fa_auth.png b/doc/profile/2fa_auth.png
index 4a4fbe68984..0caaed10805 100644
--- a/doc/profile/2fa_auth.png
+++ b/doc/profile/2fa_auth.png
Binary files differ
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/emails_on_push.md b/doc/project_services/emails_on_push.md
new file mode 100644
index 00000000000..2f9f36f962e
--- /dev/null
+++ b/doc/project_services/emails_on_push.md
@@ -0,0 +1,17 @@
+## Enabling emails on push
+
+To receive email notifications for every change that is pushed to the project, visit
+your project's **Settings > Services > Emails on push** and activate the service.
+
+In the _Recipients_ area, provide a list of emails separated by commas.
+
+You can configure any of the following settings depending on your preference.
+
++ **Push events** - Email will be triggered when a push event is recieved
++ **Tag push events** - Email will be triggered when a tag is created and pushed
++ **Send from committer** - Send notifications from the committer's email address if the domain is part of the domain GitLab is running on (e.g. `user@gitlab.com`).
++ **Disable code diffs** - Don't include possibly sensitive code diffs in notification body.
+
+---
+
+![Email on push service settings](img/emails_on_push_service.png)
diff --git a/doc/project_services/img/builds_emails_service.png b/doc/project_services/img/builds_emails_service.png
index e604dd73ffa..88943dc410e 100644
--- a/doc/project_services/img/builds_emails_service.png
+++ b/doc/project_services/img/builds_emails_service.png
Binary files differ
diff --git a/doc/project_services/img/emails_on_push_service.png b/doc/project_services/img/emails_on_push_service.png
new file mode 100644
index 00000000000..cd6f79ad1eb
--- /dev/null
+++ b/doc/project_services/img/emails_on_push_service.png
Binary files differ
diff --git a/doc/project_services/img/jira_add_gitlab_commit_message.png b/doc/project_services/img/jira_add_gitlab_commit_message.png
index 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
Binary files differ
diff --git a/doc/project_services/img/jira_add_user_to_group.png b/doc/project_services/img/jira_add_user_to_group.png
index 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
Binary files differ
diff --git a/doc/project_services/img/jira_create_new_group.png b/doc/project_services/img/jira_create_new_group.png
index edaa1326058..0609060cb05 100644
--- a/doc/project_services/img/jira_create_new_group.png
+++ b/doc/project_services/img/jira_create_new_group.png
Binary files differ
diff --git a/doc/project_services/img/jira_create_new_group_name.png b/doc/project_services/img/jira_create_new_group_name.png
index 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
Binary files differ
diff --git a/doc/project_services/img/jira_create_new_user.png b/doc/project_services/img/jira_create_new_user.png
index 57e433dd818..9eaa444ed25 100644
--- a/doc/project_services/img/jira_create_new_user.png
+++ b/doc/project_services/img/jira_create_new_user.png
Binary files differ
diff --git a/doc/project_services/img/jira_group_access.png b/doc/project_services/img/jira_group_access.png
index 47716ca6d0e..8d4657427ae 100644
--- a/doc/project_services/img/jira_group_access.png
+++ b/doc/project_services/img/jira_group_access.png
Binary files differ
diff --git a/doc/project_services/img/jira_issue_closed.png b/doc/project_services/img/jira_issue_closed.png
index cabec1ae137..acdd83702d3 100644
--- a/doc/project_services/img/jira_issue_closed.png
+++ b/doc/project_services/img/jira_issue_closed.png
Binary files differ
diff --git a/doc/project_services/img/jira_issue_reference.png b/doc/project_services/img/jira_issue_reference.png
index 15739a22dc7..1a2d9f04a6c 100644
--- a/doc/project_services/img/jira_issue_reference.png
+++ b/doc/project_services/img/jira_issue_reference.png
Binary files differ
diff --git a/doc/project_services/img/jira_issues_workflow.png b/doc/project_services/img/jira_issues_workflow.png
index 28e17be3a84..0703081d77b 100644
--- a/doc/project_services/img/jira_issues_workflow.png
+++ b/doc/project_services/img/jira_issues_workflow.png
Binary files differ
diff --git a/doc/project_services/img/jira_merge_request_close.png b/doc/project_services/img/jira_merge_request_close.png
index 1e78daf105f..47785e3ba27 100644
--- a/doc/project_services/img/jira_merge_request_close.png
+++ b/doc/project_services/img/jira_merge_request_close.png
Binary files differ
diff --git a/doc/project_services/img/jira_project_name.png b/doc/project_services/img/jira_project_name.png
index 5986fdb63fb..e785ec6140d 100644
--- a/doc/project_services/img/jira_project_name.png
+++ b/doc/project_services/img/jira_project_name.png
Binary files differ
diff --git a/doc/project_services/img/jira_reference_commit_message_in_jira_issue.png b/doc/project_services/img/jira_reference_commit_message_in_jira_issue.png
index 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
Binary files differ
diff --git a/doc/project_services/img/jira_service.png b/doc/project_services/img/jira_service.png
index 1f6628c4371..13aefce6f84 100644
--- a/doc/project_services/img/jira_service.png
+++ b/doc/project_services/img/jira_service.png
Binary files differ
diff --git a/doc/project_services/img/jira_service_close_issue.png b/doc/project_services/img/jira_service_close_issue.png
index 67dfc6144c4..eed69e80d2c 100644
--- a/doc/project_services/img/jira_service_close_issue.png
+++ b/doc/project_services/img/jira_service_close_issue.png
Binary files differ
diff --git a/doc/project_services/img/jira_service_page.png b/doc/project_services/img/jira_service_page.png
index c225daa81e1..a5b49c501ba 100644
--- a/doc/project_services/img/jira_service_page.png
+++ b/doc/project_services/img/jira_service_page.png
Binary files differ
diff --git a/doc/project_services/img/jira_submit_gitlab_merge_request.png b/doc/project_services/img/jira_submit_gitlab_merge_request.png
index 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
Binary files differ
diff --git a/doc/project_services/img/jira_user_management_link.png b/doc/project_services/img/jira_user_management_link.png
index 2745916972c..5f002b59bac 100644
--- a/doc/project_services/img/jira_user_management_link.png
+++ b/doc/project_services/img/jira_user_management_link.png
Binary files differ
diff --git a/doc/project_services/img/jira_workflow_screenshot.png b/doc/project_services/img/jira_workflow_screenshot.png
index 8635a32eb68..937a50a77d9 100644
--- a/doc/project_services/img/jira_workflow_screenshot.png
+++ b/doc/project_services/img/jira_workflow_screenshot.png
Binary files differ
diff --git a/doc/project_services/img/redmine_configuration.png b/doc/project_services/img/redmine_configuration.png
index d14e526ad33..e9d8c0d2da8 100644
--- a/doc/project_services/img/redmine_configuration.png
+++ b/doc/project_services/img/redmine_configuration.png
Binary files differ
diff --git a/doc/project_services/img/services_templates_redmine_example.png b/doc/project_services/img/services_templates_redmine_example.png
index 384d057fc8e..77c2b98e5d0 100644
--- a/doc/project_services/img/services_templates_redmine_example.png
+++ b/doc/project_services/img/services_templates_redmine_example.png
Binary files differ
diff --git a/doc/project_services/project_services.md b/doc/project_services/project_services.md
index a5af620d9be..e15d5db3253 100644
--- a/doc/project_services/project_services.md
+++ b/doc/project_services/project_services.md
@@ -30,10 +30,11 @@ 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 |
-| Emails on push | Email the commits and diff of each push to a list of recipients |
+| [Emails on push](emails_on_push.md) | Email the commits and diff of each push to a list of recipients |
| External Wiki | Replaces the link to the internal wiki with a link to an external wiki |
| Flowdock | Flowdock is a collaboration web app for technical teams |
| Gemnasium | Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities |
diff --git a/doc/public_access/public_access.md b/doc/public_access/public_access.md
index 17bb75ececd..a3921f1b89f 100644
--- a/doc/public_access/public_access.md
+++ b/doc/public_access/public_access.md
@@ -17,7 +17,7 @@ Public projects can be cloned **without any** authentication.
They will also be listed on the public access directory (`/public`).
-**Any logged in user** will have [Guest](../permissions/permissions)
+**Any logged in user** will have [Guest](../user/permissions.md)
permissions on the repository.
### Internal projects
@@ -27,8 +27,8 @@ Internal projects can be cloned by any logged in user.
They will also be listed on the public access directory (`/public`) for logged
in users.
-Any logged in user will have [Guest](../permissions/permissions) permissions on
-the repository.
+Any logged in user will have [Guest](../user/permissions.md) permissions
+on the repository.
### How to change project visibility
diff --git a/doc/raketasks/backup_hrz.png b/doc/raketasks/backup_hrz.png
index 03e50df1d76..42084717ebe 100644
--- a/doc/raketasks/backup_hrz.png
+++ b/doc/raketasks/backup_hrz.png
Binary files differ
diff --git a/doc/raketasks/check_repos_output.png b/doc/raketasks/check_repos_output.png
index 916b1685101..1f632566b00 100644
--- a/doc/raketasks/check_repos_output.png
+++ b/doc/raketasks/check_repos_output.png
Binary files differ
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/raketasks/maintenance.md b/doc/raketasks/maintenance.md
index d9dce2af480..315cb56a089 100644
--- a/doc/raketasks/maintenance.md
+++ b/doc/raketasks/maintenance.md
@@ -167,3 +167,22 @@ of those assets. Unless you are modifying the JavaScript / CSS code on your
production machine after installing the package, there should be no reason to redo
rake assets:precompile on the production machine. If you suspect that assets
have been corrupted, you should reinstall the omnibus package.
+
+## Tracking Deployments
+
+GitLab provides a Rake task that lets you track deployments in GitLab
+Performance Monitoring. This Rake task simply stores the current GitLab version
+in the GitLab Performance Monitoring database.
+
+For Omnibus-packages:
+
+```
+sudo gitlab-rake gitlab:track_deployment
+```
+
+For installations from source:
+
+```
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:track_deployment RAILS_ENV=production
+```
diff --git a/doc/security/img/two_factor_authentication_settings.png b/doc/security/img/two_factor_authentication_settings.png
index aa51ce030bb..6af5feabb13 100644
--- a/doc/security/img/two_factor_authentication_settings.png
+++ b/doc/security/img/two_factor_authentication_settings.png
Binary files differ
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/2.6-to-3.0.md b/doc/update/2.6-to-3.0.md
index 4827ef9501a..fb70eaacbc9 100644
--- a/doc/update/2.6-to-3.0.md
+++ b/doc/update/2.6-to-3.0.md
@@ -13,6 +13,10 @@
git fetch origin
git checkout v3.0.3
+# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0
+# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to
+# `gem "modernizr-rails", "2.7.1"``
+sudo -u gitlab -H vim Gemfile
# Install libs
sudo -u gitlab bundle install --without development test postgres
diff --git a/doc/update/2.9-to-3.0.md b/doc/update/2.9-to-3.0.md
index f4a997a8c5e..ce46b57c09a 100644
--- a/doc/update/2.9-to-3.0.md
+++ b/doc/update/2.9-to-3.0.md
@@ -13,6 +13,11 @@
sudo -u gitlab -H git fetch origin
sudo -u gitlab -H git checkout v3.0.3
+# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0
+# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to
+# `gem "modernizr-rails", "2.7.1"``
+sudo -u gitlab -H vim Gemfile
+
# Install gems
sudo -u gitlab -H bundle install --without development test postgres
diff --git a/doc/update/3.0-to-3.1.md b/doc/update/3.0-to-3.1.md
index a30485c42f7..6ac83f3b60d 100644
--- a/doc/update/3.0-to-3.1.md
+++ b/doc/update/3.0-to-3.1.md
@@ -25,6 +25,11 @@ sudo -u gitlab -H git checkout v3.1.0
# Install new charlock_holmes
sudo gem install charlock_holmes --version '0.6.9'
+# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0
+# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to
+# `gem "modernizr-rails", "2.7.1"``
+sudo -u gitlab -H vim Gemfile
+
# Install gems for MySQL
sudo -u gitlab -H bundle install --without development test postgres sqlite
diff --git a/doc/update/3.1-to-4.0.md b/doc/update/3.1-to-4.0.md
index f1ef4df4744..df53ed6de83 100644
--- a/doc/update/3.1-to-4.0.md
+++ b/doc/update/3.1-to-4.0.md
@@ -26,6 +26,11 @@ I wrote a bash script which will do it automatically for you. Just make sure all
sudo -u gitlab -H git fetch
sudo -u gitlab -H git checkout 4-0-stable
+# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0
+# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to
+# `gem "modernizr-rails", "2.7.1"``
+sudo -u gitlab -H vim Gemfile
+
# Install gems for MySQL
sudo -u gitlab -H bundle install --without development test postgres
diff --git a/doc/update/4.0-to-4.1.md b/doc/update/4.0-to-4.1.md
index d89d5235917..c163bfd348d 100644
--- a/doc/update/4.0-to-4.1.md
+++ b/doc/update/4.0-to-4.1.md
@@ -22,6 +22,11 @@ cd /home/gitlab/gitlab/
sudo -u gitlab -H git fetch
sudo -u gitlab -H git checkout 4-1-stable
+# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0
+# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to
+# `gem "modernizr-rails", "2.7.1"``
+sudo -u gitlab -H vim Gemfile
+
# Install gems for MySQL
sudo -u gitlab -H bundle install --without development test postgres
diff --git a/doc/update/4.1-to-4.2.md b/doc/update/4.1-to-4.2.md
index 6fe4412ff90..97367c5f347 100644
--- a/doc/update/4.1-to-4.2.md
+++ b/doc/update/4.1-to-4.2.md
@@ -17,7 +17,15 @@ sudo -u gitlab -H git fetch
sudo -u gitlab -H git checkout 4-2-stable
-# Install libs
+# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0
+# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to
+# `gem "modernizr-rails", "2.7.1"``
+sudo -u gitlab -H vim Gemfile
+
+# Run a bundle install without deployment to generate the new Gemfile
+sudo -u gitlab -H bundle install --without development test postgres --no-deployment
+
+# Install libs (with deployment this time)
sudo -u gitlab -H bundle install --without development test postgres --deployment
# update db
diff --git a/doc/update/4.2-to-5.0.md b/doc/update/4.2-to-5.0.md
index f9faf65f952..ee6de51c923 100644
--- a/doc/update/4.2-to-5.0.md
+++ b/doc/update/4.2-to-5.0.md
@@ -85,8 +85,17 @@ sudo -u git -H cp config/gitlab.yml.example config/gitlab.yml
# edit it
sudo -u git -H vim config/gitlab.yml
+# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0
+# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to
+# `gem "modernizr-rails", "2.7.1"``
+sudo -u git -H vim Gemfile
+# Run a bundle install without deployment to generate the new Gemfile
+sudo -u git -H bundle install --without development test postgres --no-deployment
+
+# Install libs (with deployment this time)
sudo -u git -H bundle install --without development test postgres --deployment
+
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
sudo -u git -H bundle exec rake gitlab:shell:setup RAILS_ENV=production
sudo -u git -H bundle exec rake gitlab:shell:build_missing_projects RAILS_ENV=production
diff --git a/doc/update/5.0-to-5.1.md b/doc/update/5.0-to-5.1.md
index 9fbd1f88515..f0fddcf83af 100644
--- a/doc/update/5.0-to-5.1.md
+++ b/doc/update/5.0-to-5.1.md
@@ -42,7 +42,17 @@ cd /home/git/gitlab
sudo rm tmp/sockets/gitlab.socket
sudo -u git -H cp config/puma.rb.example config/puma.rb
+# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0
+# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to
+# `gem "modernizr-rails", "2.7.1"``
+sudo -u git -H vim Gemfile
+
+# Run a bundle install without deployment to generate the new Gemfile
+sudo -u git -H bundle install --without development test postgres --no-deployment
+
+# Install libs (with deployment this time)
sudo -u git -H bundle install --without development test postgres --deployment
+
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
sudo -u git -H bundle exec rake migrate_merge_requests RAILS_ENV=production
sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
diff --git a/doc/update/5.1-to-5.2.md b/doc/update/5.1-to-5.2.md
index cf9c4e4f770..625fcc33852 100644
--- a/doc/update/5.1-to-5.2.md
+++ b/doc/update/5.1-to-5.2.md
@@ -40,12 +40,28 @@ sudo -u git -H git checkout v1.4.0
```bash
cd /home/git/gitlab
+# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0
+# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to
+# `gem "modernizr-rails", "2.7.1"``
+sudo -u git -H vim Gemfile
+
# MySQL
+
+# Run a bundle install without deployment to generate the new Gemfile
+sudo -u git -H bundle install --without development test postgres --no-deployment
+
+# Install libs (with deployment this time)
sudo -u git -H bundle install --without development test postgres --deployment
-#PostgreSQL
+# PostgreSQL
+
+# Run a bundle install without deployment to generate the new Gemfile
+sudo -u git -H bundle install --without development test mysql --no-deployment
+
+# Install libs (with deployment this time)
sudo -u git -H bundle install --without development test mysql --deployment
+# Both MySQL and PostgreSQL
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
diff --git a/doc/update/5.1-to-5.4.md b/doc/update/5.1-to-5.4.md
index 97a98ede070..547d453914c 100644
--- a/doc/update/5.1-to-5.4.md
+++ b/doc/update/5.1-to-5.4.md
@@ -37,12 +37,28 @@ sudo -u git -H git checkout v1.7.9 # Addresses multiple critical security vulner
```bash
cd /home/git/gitlab
+# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0
+# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to
+# `gem "modernizr-rails", "2.7.1"``
+sudo -u git -H vim Gemfile
+
# MySQL
+
+# Run a bundle install without deployment to generate the new Gemfile
+sudo -u git -H bundle install --without development test postgres --no-deployment
+
+# Install libs (with deployment this time)
sudo -u git -H bundle install --without development test postgres --deployment
-#PostgreSQL
+# PostgreSQL
+
+# Run a bundle install without deployment to generate the new Gemfile
+sudo -u git -H bundle install --without development test mysql --no-deployment
+
+# Install libs (with deployment this time)
sudo -u git -H bundle install --without development test mysql --deployment
+# Both MySQL and PostgreSQL
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
diff --git a/doc/update/5.1-to-6.0.md b/doc/update/5.1-to-6.0.md
index a3fdd92bd2f..c992c69678e 100644
--- a/doc/update/5.1-to-6.0.md
+++ b/doc/update/5.1-to-6.0.md
@@ -137,12 +137,28 @@ sudo apt-get install python-docutils
```bash
cd /home/git/gitlab
+# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0
+# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to
+# `gem "modernizr-rails", "2.7.1"``
+sudo -u git -H vim Gemfile
+
# MySQL
+
+# Run a bundle install without deployment to generate the new Gemfile
+sudo -u git -H bundle install --without development test postgres --no-deployment
+
+# Install libs (with deployment this time)
sudo -u git -H bundle install --without development test postgres --deployment
-#PostgreSQL
+# PostgreSQL
+
+# Run a bundle install without deployment to generate the new Gemfile
+sudo -u git -H bundle install --without development test mysql --no-deployment
+
+# Install libs (with deployment this time)
sudo -u git -H bundle install --without development test mysql --deployment
+# Both MySQL and PostgreSQL
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
sudo -u git -H bundle exec rake migrate_groups RAILS_ENV=production
sudo -u git -H bundle exec rake migrate_global_projects RAILS_ENV=production
diff --git a/doc/update/5.2-to-5.3.md b/doc/update/5.2-to-5.3.md
index 27613aeda07..c5254f6fb0c 100644
--- a/doc/update/5.2-to-5.3.md
+++ b/doc/update/5.2-to-5.3.md
@@ -31,12 +31,28 @@ sudo -u git -H git checkout 5-3-stable
```bash
cd /home/git/gitlab
+# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0
+# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to
+# `gem "modernizr-rails", "2.7.1"``
+sudo -u git -H vim Gemfile
+
# MySQL
+
+# Run a bundle install without deployment to generate the new Gemfile
+sudo -u git -H bundle install --without development test postgres --no-deployment
+
+# Install libs (with deployment this time)
sudo -u git -H bundle install --without development test postgres --deployment
-#PostgreSQL
+# PostgreSQL
+
+# Run a bundle install without deployment to generate the new Gemfile
+sudo -u git -H bundle install --without development test mysql --no-deployment
+
+# Install libs (with deployment this time)
sudo -u git -H bundle install --without development test mysql --deployment
+# Both MySQL and PostgreSQL
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
diff --git a/doc/update/5.3-to-5.4.md b/doc/update/5.3-to-5.4.md
index 577b9a585ff..c4a6146dcda 100644
--- a/doc/update/5.3-to-5.4.md
+++ b/doc/update/5.3-to-5.4.md
@@ -35,12 +35,28 @@ sudo -u git -H git checkout v1.7.9 # Addresses multiple critical security vulner
```bash
cd /home/git/gitlab
+# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0
+# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to
+# `gem "modernizr-rails", "2.7.1"``
+sudo -u git -H vim Gemfile
+
# MySQL
+
+# Run a bundle install without deployment to generate the new Gemfile
+sudo -u git -H bundle install --without development test postgres --no-deployment
+
+# Install libs (with deployment this time)
sudo -u git -H bundle install --without development test postgres --deployment
-#PostgreSQL
+# PostgreSQL
+
+# Run a bundle install without deployment to generate the new Gemfile
+sudo -u git -H bundle install --without development test mysql --no-deployment
+
+# Install libs (with deployment this time)
sudo -u git -H bundle install --without development test mysql --deployment
+# Both MySQL and PostgreSQL
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
diff --git a/doc/update/5.4-to-6.0.md b/doc/update/5.4-to-6.0.md
index d9c6d9bfb91..f0fee634322 100644
--- a/doc/update/5.4-to-6.0.md
+++ b/doc/update/5.4-to-6.0.md
@@ -73,12 +73,28 @@ sudo apt-get install python-docutils
```bash
cd /home/git/gitlab
+# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0
+# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to
+# `gem "modernizr-rails", "2.7.1"``
+sudo -u git -H vim Gemfile
+
# MySQL
+
+# Run a bundle install without deployment to generate the new Gemfile
+sudo -u git -H bundle install --without development test postgres --no-deployment
+
+# Install libs (with deployment this time)
sudo -u git -H bundle install --without development test postgres --deployment
# PostgreSQL
+
+# Run a bundle install without deployment to generate the new Gemfile
+sudo -u git -H bundle install --without development test mysql --no-deployment
+
+# Install libs (with deployment this time)
sudo -u git -H bundle install --without development test mysql --deployment
+# Both MySQL and PostgreSQL
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
sudo -u git -H bundle exec rake migrate_groups RAILS_ENV=production
sudo -u git -H bundle exec rake migrate_global_projects RAILS_ENV=production
diff --git a/doc/update/6.0-to-6.1.md b/doc/update/6.0-to-6.1.md
index c5eba1c01c4..409faf30902 100644
--- a/doc/update/6.0-to-6.1.md
+++ b/doc/update/6.0-to-6.1.md
@@ -50,13 +50,28 @@ sudo -u git -H git checkout v1.7.9
```bash
cd /home/git/gitlab
+# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0
+# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to
+# `gem "modernizr-rails", "2.7.1"``
+sudo -u git -H vim Gemfile
+
# MySQL
+
+# Run a bundle install without deployment to generate the new Gemfile
+sudo -u git -H bundle install --without development test postgres --no-deployment
+
+# Install libs (with deployment this time)
sudo -u git -H bundle install --without development test postgres --deployment
-#PostgreSQL
-sudo -u git -H bundle install --without development test mysql --deployment
+# PostgreSQL
+
+# Run a bundle install without deployment to generate the new Gemfile
+sudo -u git -H bundle install --without development test mysql --no-deployment
+# Install libs (with deployment this time)
+sudo -u git -H bundle install --without development test mysql --deployment
+# Both MySQL and PostgreSQL
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
sudo -u git -H bundle exec rake migrate_iids RAILS_ENV=production
sudo -u git -H bundle exec rake assets:clean RAILS_ENV=production
diff --git a/doc/update/6.1-to-6.2.md b/doc/update/6.1-to-6.2.md
index a534528108a..150c7ae1c83 100644
--- a/doc/update/6.1-to-6.2.md
+++ b/doc/update/6.1-to-6.2.md
@@ -45,13 +45,28 @@ sudo apt-get install logrotate
```bash
cd /home/git/gitlab
+# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0
+# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to
+# `gem "modernizr-rails", "2.7.1"``
+sudo -u git -H vim Gemfile
+
# MySQL
+
+# Run a bundle install without deployment to generate the new Gemfile
+sudo -u git -H bundle install --without development test postgres --no-deployment
+
+# Install libs (with deployment this time)
sudo -u git -H bundle install --without development test postgres --deployment
-#PostgreSQL
-sudo -u git -H bundle install --without development test mysql --deployment
+# PostgreSQL
+
+# Run a bundle install without deployment to generate the new Gemfile
+sudo -u git -H bundle install --without development test mysql --no-deployment
+# Install libs (with deployment this time)
+sudo -u git -H bundle install --without development test mysql --deployment
+# Both MySQL and PostgreSQL
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
sudo -u git -H bundle exec rake assets:clean RAILS_ENV=production
sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
diff --git a/doc/update/6.2-to-6.3.md b/doc/update/6.2-to-6.3.md
index b08ebde0808..b96dfb8add7 100644
--- a/doc/update/6.2-to-6.3.md
+++ b/doc/update/6.2-to-6.3.md
@@ -40,13 +40,28 @@ The gitlab-shell config changed recently, so check for config file changes and m
```bash
cd /home/git/gitlab
+# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0
+# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to
+# `gem "modernizr-rails", "2.7.1"``
+sudo -u git -H vim Gemfile
+
# MySQL
+
+# Run a bundle install without deployment to generate the new Gemfile
+sudo -u git -H bundle install --without development test postgres --no-deployment
+
+# Install libs (with deployment this time)
sudo -u git -H bundle install --without development test postgres --deployment
# PostgreSQL
-sudo -u git -H bundle install --without development test mysql --deployment
+# Run a bundle install without deployment to generate the new Gemfile
+sudo -u git -H bundle install --without development test mysql --no-deployment
+
+# Install libs (with deployment this time)
+sudo -u git -H bundle install --without development test mysql --deployment
+# Both MySQL and PostgreSQL
# Run database migrations
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
diff --git a/doc/update/6.3-to-6.4.md b/doc/update/6.3-to-6.4.md
index 951d92dfeb5..37028be055f 100644
--- a/doc/update/6.3-to-6.4.md
+++ b/doc/update/6.3-to-6.4.md
@@ -36,13 +36,28 @@ sudo -u git -H git checkout v1.8.0
```bash
cd /home/git/gitlab
+# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0
+# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to
+# `gem "modernizr-rails", "2.7.1"``
+sudo -u git -H vim Gemfile
+
# MySQL
+
+# Run a bundle install without deployment to generate the new Gemfile
+sudo -u git -H bundle install --without development test postgres --no-deployment
+
+# Install libs (with deployment this time)
sudo -u git -H bundle install --without development test postgres --deployment
# PostgreSQL
-sudo -u git -H bundle install --without development test mysql --deployment
+# Run a bundle install without deployment to generate the new Gemfile
+sudo -u git -H bundle install --without development test mysql --no-deployment
+
+# Install libs (with deployment this time)
+sudo -u git -H bundle install --without development test mysql --deployment
+# Both MySQL and PostgreSQL
# Run database migrations
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
diff --git a/doc/update/6.4-to-6.5.md b/doc/update/6.4-to-6.5.md
index 0dae9a9fe59..982381a4db0 100644
--- a/doc/update/6.4-to-6.5.md
+++ b/doc/update/6.4-to-6.5.md
@@ -46,13 +46,28 @@ sudo -u git -H git checkout v1.8.0
```bash
cd /home/git/gitlab
-# MySQL installations (note: the line below states '--without ... postgres')
+# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0
+# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to
+# `gem "modernizr-rails", "2.7.1"``
+sudo -u git -H vim Gemfile
+
+# MySQL
+
+# Run a bundle install without deployment to generate the new Gemfile
+sudo -u git -H bundle install --without development test postgres --no-deployment
+
+# Install libs (with deployment this time)
sudo -u git -H bundle install --without development test postgres --deployment
-# PostgreSQL installations (note: the line below states '--without ... mysql')
-sudo -u git -H bundle install --without development test mysql --deployment
+# PostgreSQL
+
+# Run a bundle install without deployment to generate the new Gemfile
+sudo -u git -H bundle install --without development test mysql --no-deployment
+# Install libs (with deployment this time)
+sudo -u git -H bundle install --without development test mysql --deployment
+# Both MySQL and PostgreSQL
# Run database migrations
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
diff --git a/doc/update/6.5-to-6.6.md b/doc/update/6.5-to-6.6.md
index c24e83eb006..bbed2b30215 100644
--- a/doc/update/6.5-to-6.6.md
+++ b/doc/update/6.5-to-6.6.md
@@ -46,12 +46,28 @@ sudo -u git -H git checkout v1.8.0
```bash
cd /home/git/gitlab
-# MySQL installations (note: the line below states '--without ... postgres')
+# The Modernizr gem was yanked from RubyGems. It is required for GitLab >= 2.8.0
+# Edit `Gemfile` and change `gem "modernizr", "2.5.3"` to
+# `gem "modernizr-rails", "2.7.1"``
+sudo -u git -H vim Gemfile
+
+# MySQL
+
+# Run a bundle install without deployment to generate the new Gemfile
+sudo -u git -H bundle install --without development test postgres --no-deployment
+
+# Install libs (with deployment this time)
sudo -u git -H bundle install --without development test postgres --deployment
-# PostgreSQL installations (note: the line below states '--without ... mysql')
+# PostgreSQL
+
+# Run a bundle install without deployment to generate the new Gemfile
+sudo -u git -H bundle install --without development test mysql --no-deployment
+
+# Install libs (with deployment this time)
sudo -u git -H bundle install --without development test mysql --deployment
+# Both MySQL and PostgreSQL
# Run database migrations
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
diff --git a/doc/update/6.6-to-6.7.md b/doc/update/6.6-to-6.7.md
index b4298c93429..8e82942a1a0 100644
--- a/doc/update/6.6-to-6.7.md
+++ b/doc/update/6.6-to-6.7.md
@@ -46,13 +46,23 @@ sudo -u git -H git checkout v1.9.1
```bash
cd /home/git/gitlab
-# MySQL installations (note: the line below states '--without ... postgres')
+# MySQL
+
+# Run a bundle install without deployment to generate the new Gemfile
+sudo -u git -H bundle install --without development test postgres --no-deployment
+
+# Install libs (with deployment this time)
sudo -u git -H bundle install --without development test postgres --deployment
-# PostgreSQL installations (note: the line below states '--without ... mysql')
-sudo -u git -H bundle install --without development test mysql --deployment
+# PostgreSQL
+# Run a bundle install without deployment to generate the new Gemfile
+sudo -u git -H bundle install --without development test mysql --no-deployment
+
+# Install libs (with deployment this time)
+sudo -u git -H bundle install --without development test mysql --deployment
+# Both MySQL and PostgreSQL
# Run database migrations
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
diff --git a/doc/update/6.x-or-7.x-to-7.14.md b/doc/update/6.x-or-7.x-to-7.14.md
index c45fc9340ea..f170a0021b7 100644
--- a/doc/update/6.x-or-7.x-to-7.14.md
+++ b/doc/update/6.x-or-7.x-to-7.14.md
@@ -147,12 +147,15 @@ sudo -u git -H bundle install --without development test postgres --deployment
# PostgreSQL installations (note: the line below states '--without ... mysql')
sudo -u git -H bundle install --without development test mysql --deployment
-# Run database migrations
-sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+# Run database migrations from 6.0 to 6.1
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production VERSION=20130909132950
# Enable internal issue IDs (introduced in GitLab 6.1)
sudo -u git -H bundle exec rake migrate_iids RAILS_ENV=production
+# Run left 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
diff --git a/doc/update/8.6-to-8.7.md b/doc/update/8.6-to-8.7.md
index bb463d43a7c..cb66ef920bb 100644
--- a/doc/update/8.6-to-8.7.md
+++ b/doc/update/8.6-to-8.7.md
@@ -45,7 +45,7 @@ sudo -u git -H git checkout 8-7-stable-ee
```bash
cd /home/git/gitlab-shell
-sudo -u git -H git fetch --all --tags
+sudo -u git -H git fetch --tags
sudo -u git -H git checkout v2.7.2
```
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/permissions.md b/doc/user/permissions.md
new file mode 100644
index 00000000000..66542861781
--- /dev/null
+++ b/doc/user/permissions.md
@@ -0,0 +1,131 @@
+# Permissions
+
+Users have different abilities depending on the access level they have in a
+particular group or project. If a user is both in a group's project and the
+project itself, the highest permission level is used.
+
+On public and internal projects the Guest role is not enforced. All users will
+be able to create issues, leave comments, and pull or download the project code.
+
+GitLab administrators receive all permissions.
+
+To add or import a user, you can follow the [project users and members
+documentation](../workflow/add-user/add-user.md).
+
+## Project
+
+The following table depicts the various user permission levels in a project.
+
+| Action | Guest | Reporter | Developer | Master | Owner |
+|---------------------------------------|---------|------------|-------------|----------|--------|
+| Create new issue | ✓ | ✓ | ✓ | ✓ | ✓ |
+| Leave comments | ✓ | ✓ | ✓ | ✓ | ✓ |
+| See a list of builds | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
+| See a build log | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
+| Download and browse build artifacts | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
+| Pull project code | | ✓ | ✓ | ✓ | ✓ |
+| Download project | | ✓ | ✓ | ✓ | ✓ |
+| Create code snippets | | ✓ | ✓ | ✓ | ✓ |
+| Manage issue tracker | | ✓ | ✓ | ✓ | ✓ |
+| Manage labels | | ✓ | ✓ | ✓ | ✓ |
+| See a commit status | | ✓ | ✓ | ✓ | ✓ |
+| See a container registry | | ✓ | ✓ | ✓ | ✓ |
+| See environments | | ✓ | ✓ | ✓ | ✓ |
+| Manage/Accept merge requests | | | ✓ | ✓ | ✓ |
+| Create new merge request | | | ✓ | ✓ | ✓ |
+| Create new branches | | | ✓ | ✓ | ✓ |
+| Push to non-protected branches | | | ✓ | ✓ | ✓ |
+| Force push to non-protected branches | | | ✓ | ✓ | ✓ |
+| Remove non-protected branches | | | ✓ | ✓ | ✓ |
+| Add tags | | | ✓ | ✓ | ✓ |
+| Write a wiki | | | ✓ | ✓ | ✓ |
+| Cancel and retry builds | | | ✓ | ✓ | ✓ |
+| Create or update commit status | | | ✓ | ✓ | ✓ |
+| Update a container registry | | | ✓ | ✓ | ✓ |
+| Remove a container registry image | | | ✓ | ✓ | ✓ |
+| Create new environments | | | ✓ | ✓ | ✓ |
+| Create new milestones | | | | ✓ | ✓ |
+| Add new team members | | | | ✓ | ✓ |
+| Push to protected branches | | | | ✓ | ✓ |
+| Enable/disable branch protection | | | | ✓ | ✓ |
+| Turn on/off protected branch push for devs| | | | ✓ | ✓ |
+| Rewrite/remove Git tags | | | | ✓ | ✓ |
+| Edit project | | | | ✓ | ✓ |
+| Add deploy keys to project | | | | ✓ | ✓ |
+| Configure project hooks | | | | ✓ | ✓ |
+| Manage runners | | | | ✓ | ✓ |
+| Manage build triggers | | | | ✓ | ✓ |
+| Manage variables | | | | ✓ | ✓ |
+| Delete environments | | | | ✓ | ✓ |
+| Switch visibility level | | | | | ✓ |
+| Transfer project to another namespace | | | | | ✓ |
+| Remove project | | | | | ✓ |
+| Force push to protected branches [^2] | | | | | |
+| Remove protected branches [^2] | | | | | |
+
+[^1]: If **Allow guest to access builds** is enabled in CI settings
+[^2]: Not allowed for Guest, Reporter, Developer, Master, or Owner
+
+## Group
+
+Any user can remove themselves from a group, unless they are the last Owner of
+the group. The following table depicts the various user permission levels in a
+group.
+
+| Action | Guest | Reporter | Developer | Master | Owner |
+|-------------------------|-------|----------|-----------|--------|-------|
+| Browse group | ✓ | ✓ | ✓ | ✓ | ✓ |
+| Edit group | | | | | ✓ |
+| Create project in group | | | | ✓ | ✓ |
+| Manage group members | | | | | ✓ |
+| Remove group | | | | | ✓ |
+
+## External Users
+
+In cases where it is desired that a user has access only to some internal or
+private projects, there is the option of creating **External Users**. This
+feature may be useful when for example a contractor is working on a given
+project and should only have access to that project.
+
+External users can only access projects to which they are explicitly granted
+access, thus hiding all other internal or private ones from them. Access can be
+granted by adding the user as member to the project or group.
+
+They will, like usual users, receive a role in the project or group with all
+the abilities that are mentioned in the table above. They cannot however create
+groups or projects, and they have the same access as logged out users in all
+other cases.
+
+An administrator can flag a user as external [through the API](../api/users.md)
+or by checking the checkbox on the admin panel. As an administrator, navigate
+to **Admin > Users** to create a new user or edit an existing one. There, you
+will find the option to flag the user as external.
+
+By default new users are not set as external users. This behavior can be changed
+by an administrator under **Admin > Application Settings**.
+
+## GitLab CI
+
+GitLab CI permissions rely on the role the user has in GitLab. There are four
+permission levels it total:
+
+- admin
+- master
+- developer
+- guest/reporter
+
+The admin user can perform any action on GitLab CI in scope of the GitLab
+instance and project. In addition, all admins can use the admin interface under
+`/admin/runners`.
+
+| Action | Guest, Reporter | Developer | Master | Admin |
+|---------------------------------------|-----------------|-------------|----------|--------|
+| See commits and builds | ✓ | ✓ | ✓ | ✓ |
+| Retry or cancel build | | ✓ | ✓ | ✓ |
+| Remove project | | | ✓ | ✓ |
+| Create project | | | ✓ | ✓ |
+| Change project configuration | | | ✓ | ✓ |
+| Add specific runners | | | ✓ | ✓ |
+| Add shared runners | | | | ✓ |
+| See events in the system | | | | ✓ |
+| Admin interface | | | | ✓ |
diff --git a/doc/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/user/project/img/labels_assign_label_in_new_issue.png b/doc/user/project/img/labels_assign_label_in_new_issue.png
new file mode 100644
index 00000000000..e32a35f7cda
--- /dev/null
+++ b/doc/user/project/img/labels_assign_label_in_new_issue.png
Binary files differ
diff --git a/doc/user/project/img/labels_assign_label_sidebar.png b/doc/user/project/img/labels_assign_label_sidebar.png
new file mode 100644
index 00000000000..799443af889
--- /dev/null
+++ b/doc/user/project/img/labels_assign_label_sidebar.png
Binary files differ
diff --git a/doc/user/project/img/labels_assign_label_sidebar_saved.png b/doc/user/project/img/labels_assign_label_sidebar_saved.png
new file mode 100644
index 00000000000..e7d8d69e60e
--- /dev/null
+++ b/doc/user/project/img/labels_assign_label_sidebar_saved.png
Binary files differ
diff --git a/doc/user/project/img/labels_default.png b/doc/user/project/img/labels_default.png
new file mode 100644
index 00000000000..ee0c9f889ad
--- /dev/null
+++ b/doc/user/project/img/labels_default.png
Binary files differ
diff --git a/doc/user/project/img/labels_description_tooltip.png b/doc/user/project/img/labels_description_tooltip.png
new file mode 100644
index 00000000000..0d1e3e091fb
--- /dev/null
+++ b/doc/user/project/img/labels_description_tooltip.png
Binary files differ
diff --git a/doc/user/project/img/labels_filter.png b/doc/user/project/img/labels_filter.png
new file mode 100644
index 00000000000..ed622be2d93
--- /dev/null
+++ b/doc/user/project/img/labels_filter.png
Binary files differ
diff --git a/doc/user/project/img/labels_filter_by_priority.png b/doc/user/project/img/labels_filter_by_priority.png
new file mode 100644
index 00000000000..c5a9e20919b
--- /dev/null
+++ b/doc/user/project/img/labels_filter_by_priority.png
Binary files differ
diff --git a/doc/user/project/img/labels_generate.png b/doc/user/project/img/labels_generate.png
new file mode 100644
index 00000000000..9579be4e231
--- /dev/null
+++ b/doc/user/project/img/labels_generate.png
Binary files differ
diff --git a/doc/user/project/img/labels_new_label.png b/doc/user/project/img/labels_new_label.png
new file mode 100644
index 00000000000..a916d3dceb5
--- /dev/null
+++ b/doc/user/project/img/labels_new_label.png
Binary files differ
diff --git a/doc/user/project/img/labels_new_label_on_the_fly.png b/doc/user/project/img/labels_new_label_on_the_fly.png
new file mode 100644
index 00000000000..80cc434239e
--- /dev/null
+++ b/doc/user/project/img/labels_new_label_on_the_fly.png
Binary files differ
diff --git a/doc/user/project/img/labels_new_label_on_the_fly_create.png b/doc/user/project/img/labels_new_label_on_the_fly_create.png
new file mode 100644
index 00000000000..c41090945eb
--- /dev/null
+++ b/doc/user/project/img/labels_new_label_on_the_fly_create.png
Binary files differ
diff --git a/doc/user/project/img/labels_prioritize.png b/doc/user/project/img/labels_prioritize.png
new file mode 100644
index 00000000000..8dfe72cf826
--- /dev/null
+++ b/doc/user/project/img/labels_prioritize.png
Binary files differ
diff --git a/doc/user/project/img/labels_subscribe.png b/doc/user/project/img/labels_subscribe.png
new file mode 100644
index 00000000000..ea3db2bc0cf
--- /dev/null
+++ b/doc/user/project/img/labels_subscribe.png
Binary files differ
diff --git a/doc/user/project/labels.md b/doc/user/project/labels.md
new file mode 100644
index 00000000000..4258185b7d0
--- /dev/null
+++ b/doc/user/project/labels.md
@@ -0,0 +1,147 @@
+# Labels
+
+Labels provide an easy way to categorize the issues or merge requests based on
+descriptive titles like `bug`, `documentation` or any other text you feel like
+it. They can have different colors, a description, and are visible throughout
+the issue tracker or inside each issue individually.
+
+With labels, you can navigate the issue tracker and filter any bloated
+information to visualize only the issues you are interested in. Let's see how
+that works.
+
+## Create new labels
+
+>**Note:**
+A permission level of `Developer` or higher is required in order to manage
+labels.
+
+Head over a single project and navigate to **Issues > Labels**.
+
+The first time you visit this page, you'll notice that there are no labels
+created yet.
+
+![Generate new labels](img/labels_generate.png)
+
+---
+
+You can skip that and create a new label or click that link and GitLab will
+generate a set of predefined labels for you. There 8 default generated labels
+in total and you can see them in the screenshot below.
+
+![Default generated labels](img/labels_default.png)
+
+---
+
+You can see that from the labels page you can have an overview of the number of
+issues and merge requests assigned to each label.
+
+Creating a new label from scratch is as easy as pressing the **New label**
+button. From there on you can choose the name, give it an optional description,
+a color and you are set.
+
+When you are ready press the **Create label** button to create the new label.
+
+![New label](img/labels_new_label.png)
+
+## Prioritize labels
+
+>**Notes:**
+ - This feature was introduced in GitLab 8.9.
+ - Priority sorting is based on the highest priority label only. This might
+ change in the future, follow the discussion in
+ https://gitlab.com/gitlab-org/gitlab-ce/issues/18554.
+
+Prioritized labels are like any other label, but sorted by priority. This allows
+you to sort issues and merge requests by priority.
+
+To prioritize labels, navigate to your project's **Issues > Labels** and click
+on the star icon next to them to put them in the priority list. Click on the
+star icon again to remove them from the list.
+
+From there, you can drag them around to set the desired priority. Priority is
+set from high to low with an ascending order. Labels with no priority, count as
+having their priority set to null.
+
+![Prioritize labels](img/labels_prioritize.png)
+
+Now that you have labels prioritized, you can use the 'Priority' filter in the
+issues or merge requests tracker. Those with the highest priority label, will
+appear on top.
+
+![Filter labels by priority](img/labels_filter_by_priority.png)
+
+## Subscribe to labels
+
+If you don’t want to miss issues or merge requests that are important to you,
+simply subscribe to a label. You’ll get notified whenever the label gets added
+to an issue or merge request, making sure you don’t miss a thing.
+
+Go to your project's **Issues > Labels** area, find the label(s) you want to
+subscribe to and click on the eye icon. Click again to unsubscribe.
+
+![Subscribe to labels](img/labels_subscribe.png)
+
+If you work on a large or popular project, try subscribing only to the labels
+that are relevant to you. You’ll notice it’ll be much easier to focus on what’s
+important.
+
+## Create a new label right from the issue tracker
+
+>**Note:**
+This feature was introduced in GitLab 8.6.
+
+There are times when you are already in the issue tracker searching for a
+label, only to realize it doesn't exist. Instead of going to the **Labels**
+page and being distracted from your original purpose, you can create new
+labels on the fly.
+
+Select **Create new** from the labels dropdown list, provide a name, pick a
+color and hit **Create**.
+
+![Create new label on the fly](img/labels_new_label_on_the_fly_create.png)
+![New label on the fly](img/labels_new_label_on_the_fly.png)
+
+## Assigning labels to issues and merge requests
+
+There are generally two ways to assign a label to an issue or merge request.
+
+You can assign a label when you first create or edit an issue or merge request.
+
+![Assign label in new issue](img/labels_assign_label_in_new_issue.png)
+
+---
+
+The second way is by using the right sidebar when inside an issue or merge
+request. Expand it and hit **Edit** in the labels area. Start typing the name
+of the label you are looking for to narrow down the list, and select it. You
+can add more than one labels at once. When done, click outside the sidebar area
+for the changes to take effect.
+
+![Assign label in sidebar](img/labels_assign_label_sidebar.png)
+![Save labels in sidebar](img/labels_assign_label_sidebar_saved.png)
+
+---
+
+To remove labels, expand the left sidebar and unmark them from the labels list.
+Simple as that.
+
+## Use labels to filter issues
+
+Once you start adding labels to your issues, you'll see the benefit of it.
+Labels can have several uses, one of them being the quick filtering of issues
+or merge requests.
+
+Pick an existing label from the dropdown _Label_ menu or click on an existing
+label from the issue tracker. In the latter case, you also get to see the
+label description like shown below.
+
+![Filter labels](img/labels_filter.png)
+
+---
+
+And if you added a description to your label, you can see it by hovering your
+mouse over the label in the issue tracker or wherever else the label is
+rendered.
+
+![Label tooltips](img/labels_description_tooltip.png)
+
diff --git a/doc/user/project/settings/img/import_export_download_export.png b/doc/user/project/settings/img/import_export_download_export.png
new file mode 100644
index 00000000000..a2f7f0085c1
--- /dev/null
+++ b/doc/user/project/settings/img/import_export_download_export.png
Binary files differ
diff --git a/doc/user/project/settings/img/import_export_export_button.png b/doc/user/project/settings/img/import_export_export_button.png
new file mode 100644
index 00000000000..1f7bdd21b0d
--- /dev/null
+++ b/doc/user/project/settings/img/import_export_export_button.png
Binary files differ
diff --git a/doc/user/project/settings/img/import_export_mail_link.png b/doc/user/project/settings/img/import_export_mail_link.png
new file mode 100644
index 00000000000..c123f83eb8e
--- /dev/null
+++ b/doc/user/project/settings/img/import_export_mail_link.png
Binary files differ
diff --git a/doc/user/project/settings/img/import_export_new_project.png b/doc/user/project/settings/img/import_export_new_project.png
new file mode 100644
index 00000000000..b3a7f201018
--- /dev/null
+++ b/doc/user/project/settings/img/import_export_new_project.png
Binary files differ
diff --git a/doc/user/project/settings/img/import_export_select_file.png b/doc/user/project/settings/img/import_export_select_file.png
new file mode 100644
index 00000000000..f31832af3e1
--- /dev/null
+++ b/doc/user/project/settings/img/import_export_select_file.png
Binary files differ
diff --git a/doc/user/project/settings/img/settings_edit_button.png b/doc/user/project/settings/img/settings_edit_button.png
new file mode 100644
index 00000000000..3c0cee536de
--- /dev/null
+++ b/doc/user/project/settings/img/settings_edit_button.png
Binary files differ
diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md
new file mode 100644
index 00000000000..38e9786123d
--- /dev/null
+++ b/doc/user/project/settings/import_export.md
@@ -0,0 +1,73 @@
+# Project import/export
+
+>**Notes:**
+ - This feature was [introduced][ce-3050] in GitLab 8.9
+ - Importing will not be possible if the import instance version is lower
+ than that of the exporter.
+ - For existing installations, the project import option has to be enabled in
+ application settings (`/admin/application_settings`) under 'Import sources'.
+ Ask your administrator if you don't see the **GitLab export** button when
+ creating a new project.
+ - You can find some useful raketasks if you are an administrator in the
+ [import_export](../../../administration/raketasks/project_import_export.md)
+ raketask.
+ - The exports are stored in a temporary [shared directory][tmp] and are deleted
+ every 24 hours by a specific worker.
+
+Existing projects running on any GitLab instance or GitLab.com can be exported
+with all their related data and be moved into a new GitLab instance.
+
+## Exported contents
+
+The following items will be exported:
+
+- Project and wiki repositories
+- Project uploads
+- Project configuration including web hooks and services
+- Issues with comments, merge requests with diffs and comments, labels, milestones, snippets,
+ and other project entities
+
+The following items will NOT be exported:
+
+- Build traces and artifacts
+- LFS objects
+
+## Exporting a project and its data
+
+1. Go to the project settings page by clicking on **Edit Project**:
+
+ ![Project settings button](img/settings_edit_button.png)
+
+1. Scroll down to find the **Export project** button:
+
+ ![Export button](img/import_export_export_button.png)
+
+1. Once the export is generated, you should receive an e-mail with a link to
+ download the file:
+
+ ![Email download link](img/import_export_mail_link.png)
+
+1. Alternatively, you can come back to the project settings and download the
+ file from there, or generate a new export. Once the file available, the page
+ should show the **Download export** button:
+
+ ![Download export](img/import_export_download_export.png)
+
+## Importing the project
+
+1. The new GitLab project import feature is at the far right of the import
+ options when creating a New Project. Make sure you are in the right namespace
+ and you have entered a project name. Click on **GitLab export**:
+
+ ![New project](img/import_export_new_project.png)
+
+1. You can see where the project will be imported to. You can now select file
+ exported previously:
+
+ ![Select file](img/import_export_select_file.png)
+
+1. Click on **Import project** to begin importing. Your newly imported project
+ page will appear soon.
+
+[ce-3050]: https://gitlab.com/gitlab-org/gitlab-ce/issues/3050
+[tmp]: ../../../development/shared_files.md
diff --git a/doc/web_hooks/ssl.png b/doc/web_hooks/ssl.png
index 698f1a0f64a..8c4f08d1825 100644
--- a/doc/web_hooks/ssl.png
+++ b/doc/web_hooks/ssl.png
Binary files differ
diff --git a/doc/workflow/README.md b/doc/workflow/README.md
index 9efe41308dc..ddb2f7281b1 100644
--- a/doc/workflow/README.md
+++ b/doc/workflow/README.md
@@ -7,7 +7,7 @@
- [Groups](groups.md)
- [Keyboard shortcuts](shortcuts.md)
- [File finder](file_finder.md)
-- [Labels](labels.md)
+- [Labels](../user/project/labels.md)
- [Notification emails](notifications.md)
- [Project Features](project_features.md)
- [Project forking workflow](forking_workflow.md)
diff --git a/doc/workflow/add-user/add-user.md b/doc/workflow/add-user/add-user.md
index fffa0aba57f..0537ce0bcd4 100644
--- a/doc/workflow/add-user/add-user.md
+++ b/doc/workflow/add-user/add-user.md
@@ -8,7 +8,7 @@ You should have `master` or `owner` permissions to add or import a new user
to your project.
The first step to add or import a user, go to your project and click on
-**Members** on the left side of your screen.
+**Members** in the drop-down menu on the right side of your screen.
![Members](img/add_user_members_menu.png)
@@ -23,7 +23,7 @@ want to add.
---
-Select the user and the [permission level](../../permissions/permissions.md)
+Select the user and the [permission level](../../user/permissions.md)
that you'd like to give the user. Note that you can select more than one user.
![Give user permissions](img/add_user_give_permissions.png)
@@ -87,3 +87,25 @@ invitation, change their access level or even delete them.
Once the user accepts the invitation, they will be prompted to create a new
GitLab account using the same e-mail address the invitation was sent to.
+
+## Request access to a project
+
+As a user, you can request to be a member of a project. Go to the project you'd
+like to be a member of, and click the **Request Access** button on the right
+side of your screen.
+
+![Request access button](img/request_access_button.png)
+
+---
+
+Project owners & masters will be notified of your request and will be able to approve or
+decline it on the members page.
+
+![Manage access requests](img/access_requests_management.png)
+
+---
+
+If you change your mind before your request is approved, just click the
+**Withdraw Access Request** button.
+
+![Withdraw access request button](img/withdraw_access_request_button.png)
diff --git a/doc/workflow/add-user/img/access_requests_management.png b/doc/workflow/add-user/img/access_requests_management.png
new file mode 100644
index 00000000000..5c9b510ba9d
--- /dev/null
+++ b/doc/workflow/add-user/img/access_requests_management.png
Binary files differ
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
index 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
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_email_accept.png b/doc/workflow/add-user/img/add_user_email_accept.png
index 910affc9659..a2954ad7c37 100644
--- a/doc/workflow/add-user/img/add_user_email_accept.png
+++ b/doc/workflow/add-user/img/add_user_email_accept.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_email_ready.png b/doc/workflow/add-user/img/add_user_email_ready.png
index 5f02ce89b3e..19d91bc0999 100644
--- a/doc/workflow/add-user/img/add_user_email_ready.png
+++ b/doc/workflow/add-user/img/add_user_email_ready.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_email_search.png b/doc/workflow/add-user/img/add_user_email_search.png
index 140979fbe13..cb31b77d941 100644
--- a/doc/workflow/add-user/img/add_user_email_search.png
+++ b/doc/workflow/add-user/img/add_user_email_search.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_give_permissions.png b/doc/workflow/add-user/img/add_user_give_permissions.png
index 8ef9156c8d5..e6b77022f06 100644
--- a/doc/workflow/add-user/img/add_user_give_permissions.png
+++ b/doc/workflow/add-user/img/add_user_give_permissions.png
Binary files differ
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
index 5770d5cf0c4..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
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_imported_members.png b/doc/workflow/add-user/img/add_user_imported_members.png
index dea4b3f40ad..5cd120a4245 100644
--- a/doc/workflow/add-user/img/add_user_imported_members.png
+++ b/doc/workflow/add-user/img/add_user_imported_members.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_list_members.png b/doc/workflow/add-user/img/add_user_list_members.png
index 7daa6ca7d9e..5fe3482192e 100644
--- a/doc/workflow/add-user/img/add_user_list_members.png
+++ b/doc/workflow/add-user/img/add_user_list_members.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_members_menu.png b/doc/workflow/add-user/img/add_user_members_menu.png
index f1797b95f67..340d15c9830 100644
--- a/doc/workflow/add-user/img/add_user_members_menu.png
+++ b/doc/workflow/add-user/img/add_user_members_menu.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_search_people.png b/doc/workflow/add-user/img/add_user_search_people.png
index 5ac10ce80d4..1c05d70ca31 100644
--- a/doc/workflow/add-user/img/add_user_search_people.png
+++ b/doc/workflow/add-user/img/add_user_search_people.png
Binary files differ
diff --git a/doc/workflow/add-user/img/request_access_button.png b/doc/workflow/add-user/img/request_access_button.png
new file mode 100644
index 00000000000..984d640b0f0
--- /dev/null
+++ b/doc/workflow/add-user/img/request_access_button.png
Binary files differ
diff --git a/doc/workflow/add-user/img/withdraw_access_request_button.png b/doc/workflow/add-user/img/withdraw_access_request_button.png
new file mode 100644
index 00000000000..ff54a0e4384
--- /dev/null
+++ b/doc/workflow/add-user/img/withdraw_access_request_button.png
Binary files differ
diff --git a/doc/workflow/award_emoji.md b/doc/workflow/award_emoji.md
index 70b35c58be6..e6f8b792707 100644
--- a/doc/workflow/award_emoji.md
+++ b/doc/workflow/award_emoji.md
@@ -1,28 +1,26 @@
-# Award emojis
+# Award emoji
>**Note:**
This feature was [introduced][1825] in GitLab 8.2.
When you're collaborating online, you get fewer opportunities for high-fives
-and thumbs-ups. In order to make virtual celebrations easier, you can now vote
-on issues and merge requests using emoji!
+and thumbs-ups. Emoji can be awarded to issues and merge requests, making
+virtual celebrations easier.
![Award emoji](img/award_emoji_select.png)
-This makes it much easier to give and receive feedback, without a long comment
-thread. Any comment that contains only the thumbs up or down emojis is
-converted to a vote and depicted in the emoji area.
-
-You can then use that functionality to sort issues and merge requests based on
-popularity.
+Award emoji make it much easier to give and receive feedback without a long
+comment thread. Comments that are only emoji will automatically become
+award emoji.
## Sort issues and merge requests on vote count
>**Note:**
This feature was [introduced][2871] in GitLab 8.5.
-You can quickly sort the issues or merge requests by the number of votes they
-have received. The sort option can be found in the right dropdown menu.
+You can quickly sort issues and merge requests by the number of votes they
+have received. The sort options can be found in the dropdown menu as "Most
+popular" and "Least popular".
![Votes sort options](img/award_emoji_votes_sort_options.png)
@@ -40,9 +38,28 @@ Sort by least popular issues/merge requests.
---
-The number of upvotes and downvotes is not summed up. That means that an issue
-with 18 upvotes and 5 downvotes is considered more popular than an issue with
-17 upvotes and no downvotes.
+The total number of votes is not summed up. An issue with 18 upvotes and 5
+downvotes is considered more popular than an issue with 17 upvotes and no
+downvotes.
+
+## Award emoji for comments
+
+>**Note:**
+This feature was [introduced][4291] in GitLab 8.9.
+
+Award emoji can also be applied to individual comments when you want to
+celebrate an accomplishment or agree with an opinion.
+
+To add an award emoji, click the smile in the top right of the comment and pick
+an emoji from the dropdown.
+
+![Picking an emoji for a comment](img/award_emoji_comment_picker.png)
+
+![An award emoji has been applied to a comment](img/award_emoji_comment_awarded.png)
+
+If you want to remove an award emoji, just click the emoji again and the vote
+will be removed.
[2871]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2781
[1825]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/1825
+[4291]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4291
diff --git a/doc/workflow/award_emoji.png b/doc/workflow/award_emoji.png
index fb26ee04393..481680af80c 100644
--- a/doc/workflow/award_emoji.png
+++ b/doc/workflow/award_emoji.png
Binary files differ
diff --git a/doc/workflow/ci_mr.png b/doc/workflow/ci_mr.png
index a577356f8e8..f8a7708643e 100644
--- a/doc/workflow/ci_mr.png
+++ b/doc/workflow/ci_mr.png
Binary files differ
diff --git a/doc/workflow/close_issue_mr.png b/doc/workflow/close_issue_mr.png
index a136d642e12..5e520240233 100644
--- a/doc/workflow/close_issue_mr.png
+++ b/doc/workflow/close_issue_mr.png
Binary files differ
diff --git a/doc/workflow/environment_branches.png b/doc/workflow/environment_branches.png
index ee893ced13b..13fb0478eaa 100644
--- a/doc/workflow/environment_branches.png
+++ b/doc/workflow/environment_branches.png
Binary files differ
diff --git a/doc/workflow/forking/branch_select.png b/doc/workflow/forking/branch_select.png
index 275f64d113b..7f19414f3a9 100644
--- a/doc/workflow/forking/branch_select.png
+++ b/doc/workflow/forking/branch_select.png
Binary files differ
diff --git a/doc/workflow/forking/merge_request.png b/doc/workflow/forking/merge_request.png
index 2dc00ed08a1..e2da42a2be7 100644
--- a/doc/workflow/forking/merge_request.png
+++ b/doc/workflow/forking/merge_request.png
Binary files differ
diff --git a/doc/workflow/forking_workflow.md b/doc/workflow/forking_workflow.md
index 217a4a4012f..733d079bd4a 100644
--- a/doc/workflow/forking_workflow.md
+++ b/doc/workflow/forking_workflow.md
@@ -38,7 +38,7 @@ Forking a project is in most cases a two-step process.
---
After the forking is done, you can start working on the newly created
-repository. There, you will have full [Owner](../permissions/permissions.md)
+repository. There, you will have full [Owner](../user/permissions.md)
access, so you can set it up as you please.
## Merging upstream
diff --git a/doc/workflow/four_stages.png b/doc/workflow/four_stages.png
index 2f444fc6f79..49413087dca 100644
--- a/doc/workflow/four_stages.png
+++ b/doc/workflow/four_stages.png
Binary files differ
diff --git a/doc/workflow/git_pull.png b/doc/workflow/git_pull.png
index 7d47064eb14..9a1fdf899bf 100644
--- a/doc/workflow/git_pull.png
+++ b/doc/workflow/git_pull.png
Binary files differ
diff --git a/doc/workflow/gitdashflow.png b/doc/workflow/gitdashflow.png
index f2f091dd10b..e456cf9309d 100644
--- a/doc/workflow/gitdashflow.png
+++ b/doc/workflow/gitdashflow.png
Binary files differ
diff --git a/doc/workflow/github_flow.png b/doc/workflow/github_flow.png
index 88addb623ee..b3fca97cc2d 100644
--- a/doc/workflow/github_flow.png
+++ b/doc/workflow/github_flow.png
Binary files differ
diff --git a/doc/workflow/gitlab_flow.png b/doc/workflow/gitlab_flow.png
index 1ea191a672b..d85d4ff374e 100644
--- a/doc/workflow/gitlab_flow.png
+++ b/doc/workflow/gitlab_flow.png
Binary files differ
diff --git a/doc/workflow/good_commit.png b/doc/workflow/good_commit.png
index 3737a026644..7958feea4d9 100644
--- a/doc/workflow/good_commit.png
+++ b/doc/workflow/good_commit.png
Binary files differ
diff --git a/doc/workflow/groups.md b/doc/workflow/groups.md
index 34ada1774d8..9b50286b179 100644
--- a/doc/workflow/groups.md
+++ b/doc/workflow/groups.md
@@ -51,6 +51,28 @@ 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)
+## 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
+side of your screen.
+
+![Request access button](groups/request_access_button.png)
+
+---
+
+Group owners & masters will be notified of your request and will be able to approve or
+decline it on the members page.
+
+![Manage access requests](groups/access_requests_management.png)
+
+---
+
+If you change your mind before your request is approved, just click the
+**Withdraw Access Request** button.
+
+![Withdraw access request button](groups/withdraw_access_request_button.png)
+
## Managing group memberships via LDAP
In GitLab Enterprise Edition it is possible to manage GitLab group memberships using LDAP groups.
diff --git a/doc/workflow/groups/access_requests_management.png b/doc/workflow/groups/access_requests_management.png
new file mode 100644
index 00000000000..5202434f00f
--- /dev/null
+++ b/doc/workflow/groups/access_requests_management.png
Binary files differ
diff --git a/doc/workflow/groups/add_member_to_group.png b/doc/workflow/groups/add_member_to_group.png
index fa340ce572f..6e3f660d2e4 100644
--- a/doc/workflow/groups/add_member_to_group.png
+++ b/doc/workflow/groups/add_member_to_group.png
Binary files differ
diff --git a/doc/workflow/groups/group_dashboard.png b/doc/workflow/groups/group_dashboard.png
index 7fc9048d74d..662c932e536 100644
--- a/doc/workflow/groups/group_dashboard.png
+++ b/doc/workflow/groups/group_dashboard.png
Binary files differ
diff --git a/doc/workflow/groups/group_with_two_projects.png b/doc/workflow/groups/group_with_two_projects.png
index 87242781e4f..dc3475949f5 100644
--- a/doc/workflow/groups/group_with_two_projects.png
+++ b/doc/workflow/groups/group_with_two_projects.png
Binary files differ
diff --git a/doc/workflow/groups/max_access_level.png b/doc/workflow/groups/max_access_level.png
index 71106a8a5a0..2855a514013 100644
--- a/doc/workflow/groups/max_access_level.png
+++ b/doc/workflow/groups/max_access_level.png
Binary files differ
diff --git a/doc/workflow/groups/new_group_button.png b/doc/workflow/groups/new_group_button.png
index 51e82798658..26136312c8f 100644
--- a/doc/workflow/groups/new_group_button.png
+++ b/doc/workflow/groups/new_group_button.png
Binary files differ
diff --git a/doc/workflow/groups/new_group_form.png b/doc/workflow/groups/new_group_form.png
index bf992c40bc2..dc50a069ef2 100644
--- a/doc/workflow/groups/new_group_form.png
+++ b/doc/workflow/groups/new_group_form.png
Binary files differ
diff --git a/doc/workflow/groups/other_group_sees_shared_project.png b/doc/workflow/groups/other_group_sees_shared_project.png
index cbf2c3c1fdc..2230720cecd 100644
--- a/doc/workflow/groups/other_group_sees_shared_project.png
+++ b/doc/workflow/groups/other_group_sees_shared_project.png
Binary files differ
diff --git a/doc/workflow/groups/override_access_level.png b/doc/workflow/groups/override_access_level.png
index f4225a63679..9d6aaf4c363 100644
--- a/doc/workflow/groups/override_access_level.png
+++ b/doc/workflow/groups/override_access_level.png
Binary files differ
diff --git a/doc/workflow/groups/project_members_via_group.png b/doc/workflow/groups/project_members_via_group.png
index b13cb1cfd95..58270936a0b 100644
--- a/doc/workflow/groups/project_members_via_group.png
+++ b/doc/workflow/groups/project_members_via_group.png
Binary files differ
diff --git a/doc/workflow/groups/request_access_button.png b/doc/workflow/groups/request_access_button.png
new file mode 100644
index 00000000000..0eec5cb937d
--- /dev/null
+++ b/doc/workflow/groups/request_access_button.png
Binary files differ
diff --git a/doc/workflow/groups/share_project_with_groups.png b/doc/workflow/groups/share_project_with_groups.png
index a5dbc89fe90..5772d4deced 100644
--- a/doc/workflow/groups/share_project_with_groups.png
+++ b/doc/workflow/groups/share_project_with_groups.png
Binary files differ
diff --git a/doc/workflow/groups/transfer_project.png b/doc/workflow/groups/transfer_project.png
index 044fe10d073..0aef3ab3f0f 100644
--- a/doc/workflow/groups/transfer_project.png
+++ b/doc/workflow/groups/transfer_project.png
Binary files differ
diff --git a/doc/workflow/groups/withdraw_access_request_button.png b/doc/workflow/groups/withdraw_access_request_button.png
new file mode 100644
index 00000000000..b7de830a780
--- /dev/null
+++ b/doc/workflow/groups/withdraw_access_request_button.png
Binary files differ
diff --git a/doc/workflow/img/award_emoji_comment_awarded.png b/doc/workflow/img/award_emoji_comment_awarded.png
new file mode 100644
index 00000000000..67697831869
--- /dev/null
+++ b/doc/workflow/img/award_emoji_comment_awarded.png
Binary files differ
diff --git a/doc/workflow/img/award_emoji_comment_picker.png b/doc/workflow/img/award_emoji_comment_picker.png
new file mode 100644
index 00000000000..d9c3faecdca
--- /dev/null
+++ b/doc/workflow/img/award_emoji_comment_picker.png
Binary files differ
diff --git a/doc/workflow/img/award_emoji_select.png b/doc/workflow/img/award_emoji_select.png
index fffdfedda5d..ad664c0aeff 100644
--- a/doc/workflow/img/award_emoji_select.png
+++ b/doc/workflow/img/award_emoji_select.png
Binary files differ
diff --git a/doc/workflow/img/award_emoji_votes_least_popular.png b/doc/workflow/img/award_emoji_votes_least_popular.png
index 2ef5be7154f..57d595d9602 100644
--- a/doc/workflow/img/award_emoji_votes_least_popular.png
+++ b/doc/workflow/img/award_emoji_votes_least_popular.png
Binary files differ
diff --git a/doc/workflow/img/award_emoji_votes_most_popular.png b/doc/workflow/img/award_emoji_votes_most_popular.png
index 5b089730d93..432bd09b8a7 100644
--- a/doc/workflow/img/award_emoji_votes_most_popular.png
+++ b/doc/workflow/img/award_emoji_votes_most_popular.png
Binary files differ
diff --git a/doc/workflow/img/award_emoji_votes_sort_options.png b/doc/workflow/img/award_emoji_votes_sort_options.png
index 9bbf3f82a0b..ae6e224b317 100644
--- a/doc/workflow/img/award_emoji_votes_sort_options.png
+++ b/doc/workflow/img/award_emoji_votes_sort_options.png
Binary files differ
diff --git a/doc/workflow/img/cherry_pick_changes_commit.png b/doc/workflow/img/cherry_pick_changes_commit.png
index ae91d2cae53..7fb68cc9e9b 100644
--- a/doc/workflow/img/cherry_pick_changes_commit.png
+++ b/doc/workflow/img/cherry_pick_changes_commit.png
Binary files differ
diff --git a/doc/workflow/img/cherry_pick_changes_commit_modal.png b/doc/workflow/img/cherry_pick_changes_commit_modal.png
index f502f87677a..5267e04562f 100644
--- a/doc/workflow/img/cherry_pick_changes_commit_modal.png
+++ b/doc/workflow/img/cherry_pick_changes_commit_modal.png
Binary files differ
diff --git a/doc/workflow/img/cherry_pick_changes_mr.png b/doc/workflow/img/cherry_pick_changes_mr.png
index 59c610e620b..975fb13e463 100644
--- a/doc/workflow/img/cherry_pick_changes_mr.png
+++ b/doc/workflow/img/cherry_pick_changes_mr.png
Binary files differ
diff --git a/doc/workflow/img/cherry_pick_changes_mr_modal.png b/doc/workflow/img/cherry_pick_changes_mr_modal.png
index 96a80f4726d..6c003bacbe3 100644
--- a/doc/workflow/img/cherry_pick_changes_mr_modal.png
+++ b/doc/workflow/img/cherry_pick_changes_mr_modal.png
Binary files differ
diff --git a/doc/workflow/img/file_finder_find_button.png b/doc/workflow/img/file_finder_find_button.png
index c5005d0d7ca..96e383f0213 100644
--- a/doc/workflow/img/file_finder_find_button.png
+++ b/doc/workflow/img/file_finder_find_button.png
Binary files differ
diff --git a/doc/workflow/img/file_finder_find_file.png b/doc/workflow/img/file_finder_find_file.png
index 58500f4c163..c6508514c76 100644
--- a/doc/workflow/img/file_finder_find_file.png
+++ b/doc/workflow/img/file_finder_find_file.png
Binary files differ
diff --git a/doc/workflow/img/forking_workflow_choose_namespace.png b/doc/workflow/img/forking_workflow_choose_namespace.png
index eefe5769554..1839d5e8be2 100644
--- a/doc/workflow/img/forking_workflow_choose_namespace.png
+++ b/doc/workflow/img/forking_workflow_choose_namespace.png
Binary files differ
diff --git a/doc/workflow/img/forking_workflow_fork_button.png b/doc/workflow/img/forking_workflow_fork_button.png
index 49e68d33e89..cc79d6fd40c 100644
--- a/doc/workflow/img/forking_workflow_fork_button.png
+++ b/doc/workflow/img/forking_workflow_fork_button.png
Binary files differ
diff --git a/doc/workflow/img/forking_workflow_path_taken_error.png b/doc/workflow/img/forking_workflow_path_taken_error.png
index 7a3139506fe..a859155aef0 100644
--- a/doc/workflow/img/forking_workflow_path_taken_error.png
+++ b/doc/workflow/img/forking_workflow_path_taken_error.png
Binary files differ
diff --git a/doc/workflow/img/new_branch_from_issue.png b/doc/workflow/img/new_branch_from_issue.png
index 394c139e17e..61acdd30ae9 100644
--- a/doc/workflow/img/new_branch_from_issue.png
+++ b/doc/workflow/img/new_branch_from_issue.png
Binary files differ
diff --git a/doc/workflow/img/revert_changes_commit.png b/doc/workflow/img/revert_changes_commit.png
index d84211e20db..e7194fc3504 100644
--- a/doc/workflow/img/revert_changes_commit.png
+++ b/doc/workflow/img/revert_changes_commit.png
Binary files differ
diff --git a/doc/workflow/img/revert_changes_commit_modal.png b/doc/workflow/img/revert_changes_commit_modal.png
index e94d151a2af..c660ec7eaec 100644
--- a/doc/workflow/img/revert_changes_commit_modal.png
+++ b/doc/workflow/img/revert_changes_commit_modal.png
Binary files differ
diff --git a/doc/workflow/img/revert_changes_mr.png b/doc/workflow/img/revert_changes_mr.png
index 7adad88463b..3002f0ac1c5 100644
--- a/doc/workflow/img/revert_changes_mr.png
+++ b/doc/workflow/img/revert_changes_mr.png
Binary files differ
diff --git a/doc/workflow/img/revert_changes_mr_modal.png b/doc/workflow/img/revert_changes_mr_modal.png
index 9da78f84828..c6aaeecc8a6 100644
--- a/doc/workflow/img/revert_changes_mr_modal.png
+++ b/doc/workflow/img/revert_changes_mr_modal.png
Binary files differ
diff --git a/doc/workflow/img/todo_list_item.png b/doc/workflow/img/todo_list_item.png
new file mode 100644
index 00000000000..884ba1d22a3
--- /dev/null
+++ b/doc/workflow/img/todo_list_item.png
Binary files differ
diff --git a/doc/workflow/img/todos_add_todo_sidebar.png b/doc/workflow/img/todos_add_todo_sidebar.png
new file mode 100644
index 00000000000..126ecc2c82f
--- /dev/null
+++ b/doc/workflow/img/todos_add_todo_sidebar.png
Binary files differ
diff --git a/doc/workflow/img/todos_icon.png b/doc/workflow/img/todos_icon.png
index 879b3b51c21..bba77f88913 100644
--- a/doc/workflow/img/todos_icon.png
+++ b/doc/workflow/img/todos_icon.png
Binary files differ
diff --git a/doc/workflow/img/todos_index.png b/doc/workflow/img/todos_index.png
index 4ee18dd1285..f1438ef7355 100644
--- a/doc/workflow/img/todos_index.png
+++ b/doc/workflow/img/todos_index.png
Binary files differ
diff --git a/doc/workflow/img/todos_mark_done_sidebar.png b/doc/workflow/img/todos_mark_done_sidebar.png
new file mode 100644
index 00000000000..f449f977dd6
--- /dev/null
+++ b/doc/workflow/img/todos_mark_done_sidebar.png
Binary files differ
diff --git a/doc/workflow/img/web_editor_new_branch_dropdown.png b/doc/workflow/img/web_editor_new_branch_dropdown.png
index 009e4b05adf..a8e635d2faf 100644
--- a/doc/workflow/img/web_editor_new_branch_dropdown.png
+++ b/doc/workflow/img/web_editor_new_branch_dropdown.png
Binary files differ
diff --git a/doc/workflow/img/web_editor_new_branch_page.png b/doc/workflow/img/web_editor_new_branch_page.png
index dd6cfc6e7bb..7f36b7faf63 100644
--- a/doc/workflow/img/web_editor_new_branch_page.png
+++ b/doc/workflow/img/web_editor_new_branch_page.png
Binary files differ
diff --git a/doc/workflow/img/web_editor_new_directory_dialog.png b/doc/workflow/img/web_editor_new_directory_dialog.png
index 2c76f84f395..d16e3c67116 100644
--- a/doc/workflow/img/web_editor_new_directory_dialog.png
+++ b/doc/workflow/img/web_editor_new_directory_dialog.png
Binary files differ
diff --git a/doc/workflow/img/web_editor_new_directory_dropdown.png b/doc/workflow/img/web_editor_new_directory_dropdown.png
index cedf46aedfd..c8d77b16ee8 100644
--- a/doc/workflow/img/web_editor_new_directory_dropdown.png
+++ b/doc/workflow/img/web_editor_new_directory_dropdown.png
Binary files differ
diff --git a/doc/workflow/img/web_editor_new_file_dropdown.png b/doc/workflow/img/web_editor_new_file_dropdown.png
index 6e884f6504d..3fcb91c9b93 100644
--- a/doc/workflow/img/web_editor_new_file_dropdown.png
+++ b/doc/workflow/img/web_editor_new_file_dropdown.png
Binary files differ
diff --git a/doc/workflow/img/web_editor_new_file_editor.png b/doc/workflow/img/web_editor_new_file_editor.png
index c76473bcfa7..21c340b9288 100644
--- a/doc/workflow/img/web_editor_new_file_editor.png
+++ b/doc/workflow/img/web_editor_new_file_editor.png
Binary files differ
diff --git a/doc/workflow/img/web_editor_new_push_widget.png b/doc/workflow/img/web_editor_new_push_widget.png
index a2108735741..c7738a4c930 100644
--- a/doc/workflow/img/web_editor_new_push_widget.png
+++ b/doc/workflow/img/web_editor_new_push_widget.png
Binary files differ
diff --git a/doc/workflow/img/web_editor_new_tag_dropdown.png b/doc/workflow/img/web_editor_new_tag_dropdown.png
index 263dd635b95..ac7415009b3 100644
--- a/doc/workflow/img/web_editor_new_tag_dropdown.png
+++ b/doc/workflow/img/web_editor_new_tag_dropdown.png
Binary files differ
diff --git a/doc/workflow/img/web_editor_new_tag_page.png b/doc/workflow/img/web_editor_new_tag_page.png
index 64d7cd11ed1..231e1a13fc0 100644
--- a/doc/workflow/img/web_editor_new_tag_page.png
+++ b/doc/workflow/img/web_editor_new_tag_page.png
Binary files differ
diff --git a/doc/workflow/img/web_editor_start_new_merge_request.png b/doc/workflow/img/web_editor_start_new_merge_request.png
index 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
Binary files differ
diff --git a/doc/workflow/img/web_editor_upload_file_dialog.png b/doc/workflow/img/web_editor_upload_file_dialog.png
index 6dd2207bca0..9d6d8250bbe 100644
--- a/doc/workflow/img/web_editor_upload_file_dialog.png
+++ b/doc/workflow/img/web_editor_upload_file_dialog.png
Binary files differ
diff --git a/doc/workflow/img/web_editor_upload_file_dropdown.png b/doc/workflow/img/web_editor_upload_file_dropdown.png
index bf6528701b0..6b5205b05ec 100644
--- a/doc/workflow/img/web_editor_upload_file_dropdown.png
+++ b/doc/workflow/img/web_editor_upload_file_dropdown.png
Binary files differ
diff --git a/doc/workflow/importing/bitbucket_importer/bitbucket_import_select_project.png b/doc/workflow/importing/bitbucket_importer/bitbucket_import_select_project.png
index 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
Binary files differ
diff --git a/doc/workflow/importing/fogbugz_importer/fogbugz_import_finished.png b/doc/workflow/importing/fogbugz_importer/fogbugz_import_finished.png
index 205c515bd3f..fd7a4d3fabf 100644
--- a/doc/workflow/importing/fogbugz_importer/fogbugz_import_finished.png
+++ b/doc/workflow/importing/fogbugz_importer/fogbugz_import_finished.png
Binary files differ
diff --git a/doc/workflow/importing/fogbugz_importer/fogbugz_import_login.png b/doc/workflow/importing/fogbugz_importer/fogbugz_import_login.png
index a1e348d46ad..fd1ba6f5884 100644
--- a/doc/workflow/importing/fogbugz_importer/fogbugz_import_login.png
+++ b/doc/workflow/importing/fogbugz_importer/fogbugz_import_login.png
Binary files differ
diff --git a/doc/workflow/importing/fogbugz_importer/fogbugz_import_select_fogbogz.png b/doc/workflow/importing/fogbugz_importer/fogbugz_import_select_fogbogz.png
index 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
Binary files differ
diff --git a/doc/workflow/importing/fogbugz_importer/fogbugz_import_select_project.png b/doc/workflow/importing/fogbugz_importer/fogbugz_import_select_project.png
index 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
Binary files differ
diff --git a/doc/workflow/importing/fogbugz_importer/fogbugz_import_user_map.png b/doc/workflow/importing/fogbugz_importer/fogbugz_import_user_map.png
index 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
Binary files differ
diff --git a/doc/workflow/importing/gitlab_importer/importer.png b/doc/workflow/importing/gitlab_importer/importer.png
index d2a286d8cac..35a7ddc8318 100644
--- a/doc/workflow/importing/gitlab_importer/importer.png
+++ b/doc/workflow/importing/gitlab_importer/importer.png
Binary files differ
diff --git a/doc/workflow/importing/gitlab_importer/new_project_page.png b/doc/workflow/importing/gitlab_importer/new_project_page.png
index 5e239208e1e..81074d2d016 100644
--- a/doc/workflow/importing/gitlab_importer/new_project_page.png
+++ b/doc/workflow/importing/gitlab_importer/new_project_page.png
Binary files differ
diff --git a/doc/workflow/importing/img/import_projects_from_github_importer.png b/doc/workflow/importing/img/import_projects_from_github_importer.png
index 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
Binary files differ
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
index 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
Binary files differ
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/labels.md b/doc/workflow/labels.md
index 6e4840ca5ae..5c09891dfdd 100644
--- a/doc/workflow/labels.md
+++ b/doc/workflow/labels.md
@@ -1,18 +1,3 @@
# Labels
-In GitLab, you can easily tag issues and Merge Requests. If you have permission level `Developer` or higher, you can manage labels. To create, edit or delete a label, go to a project and then to `Issues` and then `Labels`.
-
-Here you can create a new label.
-
-![new label](labels/label1.png)
-
-You can choose to set a color.
-
-![label color](labels/label2.png)
-
-If you want to change an existing label, press edit next to the listed label.
-You will be presented with the same form as when creating a new label.
-
-![edit label](labels/label3.png)
-
-You can add labels to Merge Requests when you create or edit them.
+This document was moved to [user/project/labels.md](../user/project/labels.md).
diff --git a/doc/workflow/labels/label1.png b/doc/workflow/labels/label1.png
deleted file mode 100644
index cac661a34c8..00000000000
--- a/doc/workflow/labels/label1.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/labels/label2.png b/doc/workflow/labels/label2.png
deleted file mode 100644
index 44d9fef86d4..00000000000
--- a/doc/workflow/labels/label2.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/labels/label3.png b/doc/workflow/labels/label3.png
deleted file mode 100644
index e2fce11b7a4..00000000000
--- a/doc/workflow/labels/label3.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/merge_commits.png b/doc/workflow/merge_commits.png
index 757b589d0db..8aa1587cde6 100644
--- a/doc/workflow/merge_commits.png
+++ b/doc/workflow/merge_commits.png
Binary files differ
diff --git a/doc/workflow/merge_request.png b/doc/workflow/merge_request.png
index fde3ff5c854..6aad1d82f6e 100644
--- a/doc/workflow/merge_request.png
+++ b/doc/workflow/merge_request.png
Binary files differ
diff --git a/doc/workflow/merge_requests/commit_compare.png b/doc/workflow/merge_requests/commit_compare.png
index dfd7ee220f0..0e4a2b23c04 100644
--- a/doc/workflow/merge_requests/commit_compare.png
+++ b/doc/workflow/merge_requests/commit_compare.png
Binary files differ
diff --git a/doc/workflow/merge_requests/merge_request_diff.png b/doc/workflow/merge_requests/merge_request_diff.png
index f368423c746..3ebbfb75ea3 100644
--- a/doc/workflow/merge_requests/merge_request_diff.png
+++ b/doc/workflow/merge_requests/merge_request_diff.png
Binary files differ
diff --git a/doc/workflow/merge_requests/merge_request_diff_without_whitespace.png b/doc/workflow/merge_requests/merge_request_diff_without_whitespace.png
index 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
Binary files differ
diff --git a/doc/workflow/merge_when_build_succeeds/enable.png b/doc/workflow/merge_when_build_succeeds/enable.png
index 633efa1246f..b86e6d7b3fd 100644
--- a/doc/workflow/merge_when_build_succeeds/enable.png
+++ b/doc/workflow/merge_when_build_succeeds/enable.png
Binary files differ
diff --git a/doc/workflow/merge_when_build_succeeds/status.png b/doc/workflow/merge_when_build_succeeds/status.png
index c856c7d14dc..f3ea61d8147 100644
--- a/doc/workflow/merge_when_build_succeeds/status.png
+++ b/doc/workflow/merge_when_build_succeeds/status.png
Binary files differ
diff --git a/doc/workflow/messy_flow.png b/doc/workflow/messy_flow.png
index 1addb95ca54..8d2c0dae8c2 100644
--- a/doc/workflow/messy_flow.png
+++ b/doc/workflow/messy_flow.png
Binary files differ
diff --git a/doc/workflow/milestones/form.png b/doc/workflow/milestones/form.png
index de44c1ffc1a..3965ca4d083 100644
--- a/doc/workflow/milestones/form.png
+++ b/doc/workflow/milestones/form.png
Binary files differ
diff --git a/doc/workflow/milestones/group_form.png b/doc/workflow/milestones/group_form.png
index 38862dcca68..ff20df8081f 100644
--- a/doc/workflow/milestones/group_form.png
+++ b/doc/workflow/milestones/group_form.png
Binary files differ
diff --git a/doc/workflow/mr_inline_comments.png b/doc/workflow/mr_inline_comments.png
index e851b95bcef..af7df3100d0 100644
--- a/doc/workflow/mr_inline_comments.png
+++ b/doc/workflow/mr_inline_comments.png
Binary files differ
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
index 7c6857aad1a..d50757beffc 100644
--- a/doc/workflow/notifications/settings.png
+++ b/doc/workflow/notifications/settings.png
Binary files differ
diff --git a/doc/workflow/production_branch.png b/doc/workflow/production_branch.png
index 33fb26dd621..d88a3687151 100644
--- a/doc/workflow/production_branch.png
+++ b/doc/workflow/production_branch.png
Binary files differ
diff --git a/doc/workflow/protected_branches.md b/doc/workflow/protected_branches.md
index d854ec1e025..5c1c7b47c8a 100644
--- a/doc/workflow/protected_branches.md
+++ b/doc/workflow/protected_branches.md
@@ -1,4 +1,4 @@
-# Protected branches
+# Protected Branches
Permissions in GitLab are fundamentally defined around the idea of having read or write permission to the repository and branches.
@@ -12,7 +12,7 @@ A protected branch does three simple things:
You can make any branch a protected branch. GitLab makes the master branch a protected branch by default.
-To protect a branch, user needs to have at least a Master permission level, see [permissions document](../permissions/permissions.md).
+To protect a branch, user needs to have at least a Master permission level, see [permissions document](../user/permissions.md).
![protected branches page](protected_branches/protected_branches1.png)
@@ -28,4 +28,28 @@ For those workflows, you can allow everyone with write access to push to a prote
On already protected branches you can also allow developers to push to the repository by selecting the `Developers can push` check box.
-![Developers can push](protected_branches/protected_branches2.png) \ No newline at end of file
+![Developers can push](protected_branches/protected_branches2.png)
+
+## Wildcard Protected Branches
+
+>**Note:**
+This feature was added in GitLab 8.10.
+
+1. You can specify a wildcard protected branch, which will protect all branches matching the wildcard. For example:
+
+ | Wildcard Protected Branch | Matching Branches |
+ |---------------------------+--------------------------------------------------------|
+ | `*-stable` | `production-stable`, `staging-stable` |
+ | `production/*` | `production/app-server`, `production/load-balancer` |
+ | `*gitlab*` | `gitlab`, `gitlab/staging`, `master/gitlab/production` |
+
+1. Protected branch settings (like "Developers Can Push") apply to all matching branches.
+
+1. Two different wildcards can potentially match the same branch. For example, `*-stable` and `production-*` would both match a `production-stable` branch.
+ >**Note:**
+ If _any_ of these protected branches have "Developers Can Push" set to true, then `production-stable` has it set to true.
+
+1. If you click on a protected branch's name, you will be presented with a list of all matching branches:
+
+ ![protected branch matches](protected_branches/protected_branches3.png)
+
diff --git a/doc/workflow/protected_branches/protected_branches1.png b/doc/workflow/protected_branches/protected_branches1.png
index 5c2a3de5f70..c00443803de 100644
--- a/doc/workflow/protected_branches/protected_branches1.png
+++ b/doc/workflow/protected_branches/protected_branches1.png
Binary files differ
diff --git a/doc/workflow/protected_branches/protected_branches2.png b/doc/workflow/protected_branches/protected_branches2.png
index 2dca3541365..a4f664d3b21 100644
--- a/doc/workflow/protected_branches/protected_branches2.png
+++ b/doc/workflow/protected_branches/protected_branches2.png
Binary files differ
diff --git a/doc/workflow/protected_branches/protected_branches3.png b/doc/workflow/protected_branches/protected_branches3.png
new file mode 100644
index 00000000000..2a50cb174bb
--- /dev/null
+++ b/doc/workflow/protected_branches/protected_branches3.png
Binary files differ
diff --git a/doc/workflow/rebase.png b/doc/workflow/rebase.png
index ef82c834755..df353311fa0 100644
--- a/doc/workflow/rebase.png
+++ b/doc/workflow/rebase.png
Binary files differ
diff --git a/doc/workflow/release_branches.png b/doc/workflow/release_branches.png
index da7ae53413a..c2162248d25 100644
--- a/doc/workflow/release_branches.png
+++ b/doc/workflow/release_branches.png
Binary files differ
diff --git a/doc/workflow/releases/new_tag.png b/doc/workflow/releases/new_tag.png
index e2b64bfe17f..2456a8500f4 100644
--- a/doc/workflow/releases/new_tag.png
+++ b/doc/workflow/releases/new_tag.png
Binary files differ
diff --git a/doc/workflow/releases/tags.png b/doc/workflow/releases/tags.png
index aca91906c68..eeda967afd6 100644
--- a/doc/workflow/releases/tags.png
+++ b/doc/workflow/releases/tags.png
Binary files differ
diff --git a/doc/workflow/remove_checkbox.png b/doc/workflow/remove_checkbox.png
index 3e247d38155..3b0393deb0f 100644
--- a/doc/workflow/remove_checkbox.png
+++ b/doc/workflow/remove_checkbox.png
Binary files differ
diff --git a/doc/workflow/share_with_group.png b/doc/workflow/share_with_group.png
index a0ca6f14552..2c47625e29a 100644
--- a/doc/workflow/share_with_group.png
+++ b/doc/workflow/share_with_group.png
Binary files differ
diff --git a/doc/workflow/shortcuts.png b/doc/workflow/shortcuts.png
index beb6c53ec77..a9b1c4b4dcc 100644
--- a/doc/workflow/shortcuts.png
+++ b/doc/workflow/shortcuts.png
Binary files differ
diff --git a/doc/workflow/todos.md b/doc/workflow/todos.md
index 5f440fdafdd..9524ffd5420 100644
--- a/doc/workflow/todos.md
+++ b/doc/workflow/todos.md
@@ -1,4 +1,4 @@
-# GitLab ToDos
+# GitLab Todos
>**Note:** This feature was [introduced][ce-2817] in GitLab 8.5.
@@ -14,8 +14,9 @@ in a simple dashboard.
---
-You can access quickly your Todos dashboard by clicking the round gray icon
-next to the search bar in the upper right corner.
+You can quickly access the Todos dashboard using the bell icon next to the
+search bar in the upper right corner. The number in blue is the number of Todos
+you still have open.
![Todos icon](img/todos_icon.png)
@@ -29,45 +30,61 @@ A Todo appears in your Todos dashboard when:
>**Note:** Commenting on a commit will _not_ trigger a Todo.
-## How a Todo is marked as Done
+### Manually creating a Todo
+
+You can also add an issue or merge request to your Todos dashboard by clicking
+the "Add Todo" button in the issue or merge request sidebar.
+
+![Adding a Todo from the issuable sidebar](img/todos_add_todo_sidebar.png)
+
+## Marking a Todo as done
Any action to the corresponding issue or merge request will mark your Todo as
-**Done**. This action can include:
+**Done**. Actions that dismiss Todos include:
- changing the assignee
- changing the milestone
- adding/removing a label
- commenting on the issue
-In case where you think no action is needed, you can manually mark the todo as
-done by clicking the corresponding **Done** button, and it will disappear from
-your Todos list. If you want to mark all your Todos as done, just click on the
-**Mark all as done** button.
-
---
-In order for a Todo to be marked as done, the action must be coming from you.
-So, if you close the related issue or merge the merge request yourself, and you
-had a Todo for that, it will automatically get marked as done. On the other
-hand, if someone else closes, merges or takes action on the issue or merge
-request, your Todo will remain pending. This makes sense because you may need
-to give attention to an issue even if it has been resolved.
+Todos are personal, and they're only marked as done if the action is coming from
+you. If you close the issue or merge request, your Todo will automatically
+be marked as done.
+
+If someone else closes, merges, or takes action on the issue or merge
+request, your Todo will remain pending. This prevents other users from closing issues without you being notified.
There is just one Todo per issue or merge request, so mentioning a user a
hundred times in an issue will only trigger one Todo.
+---
+
+If no action is needed, you can manually mark the Todo as done by clicking the
+corresponding **Done** button, and it will disappear from your Todo list.
+
+![A Todo in the Todos dashboard](img/todo_list_item.png)
+
+A Todo can also be marked as done from the issue or merge request sidebar using
+the "Mark Done" button.
+
+![Mark Done from the issuable sidebar](img/todos_mark_done_sidebar.png)
+
+You can mark all your Todos as done at once by clicking on the **Mark all as
+done** button.
+
## Filtering your Todos
-In general, there are four kinds of filters you can use on your Todos
-dashboard:
+There are four kinds of filters you can use on your Todos dashboard.
-| Filter | Description |
-| ------ | ----------- |
+| Filter | Description |
+| ------- | ----------- |
| Project | Filter by project |
| Author | Filter by the author that triggered the Todo |
| Type | Filter by issue or merge request |
| Action | Filter by the action that triggered the Todo (Assigned or Mentioned)|
-You can choose more than one filters at the same time.
+You can also filter by more than one of these at the same time.
[ce-2817]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2817
diff --git a/doc/workflow/wip_merge_requests/blocked_accept_button.png b/doc/workflow/wip_merge_requests/blocked_accept_button.png
index 4791e5de972..89c458aa8d9 100644
--- a/doc/workflow/wip_merge_requests/blocked_accept_button.png
+++ b/doc/workflow/wip_merge_requests/blocked_accept_button.png
Binary files differ
diff --git a/doc/workflow/wip_merge_requests/mark_as_wip.png b/doc/workflow/wip_merge_requests/mark_as_wip.png
index 8fa83a201ac..9c37354a653 100644
--- a/doc/workflow/wip_merge_requests/mark_as_wip.png
+++ b/doc/workflow/wip_merge_requests/mark_as_wip.png
Binary files differ
diff --git a/doc/workflow/wip_merge_requests/unmark_as_wip.png b/doc/workflow/wip_merge_requests/unmark_as_wip.png
index d45e68f31c5..31f7326beb0 100644
--- a/doc/workflow/wip_merge_requests/unmark_as_wip.png
+++ b/doc/workflow/wip_merge_requests/unmark_as_wip.png
Binary files differ
diff --git a/features/admin/groups.feature b/features/admin/groups.feature
index ab7de7ac315..657e847cf4a 100644
--- a/features/admin/groups.feature
+++ b/features/admin/groups.feature
@@ -27,13 +27,6 @@ Feature: Admin Groups
Then I should see project shared with group
@javascript
- Scenario: Remove user from group
- Given we have user "John Doe" in group
- When I visit admin group page
- And I remove user "John Doe" from group
- Then I should not see "John Doe" in team list
-
- @javascript
Scenario: Invite user to a group by e-mail
When I visit admin group page
When I select user "johndoe@gitlab.com" from user list as "Reporter"
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/group.feature b/features/dashboard/group.feature
index e3c01db2ebb..3ae2c679dc1 100644
--- a/features/dashboard/group.feature
+++ b/features/dashboard/group.feature
@@ -5,53 +5,9 @@ Feature: Dashboard Group
And "John Doe" is owner of group "Owned"
And "John Doe" is guest of group "Guest"
- # Leave groups
-
- @javascript
- Scenario: Owner should be able to leave from group if he is not the last owner
- Given "Mary Jane" is owner of group "Owned"
- When I visit dashboard groups page
- Then I should see group "Owned" in group list
- Then I should see group "Guest" in group list
- When I click on the "Leave" button for group "Owned"
- And I visit dashboard groups page
- Then I should not see group "Owned" in group list
- Then I should see group "Guest" in group list
-
- @javascript
- Scenario: Owner should not be able to leave from group if he is the last owner
- Given "Mary Jane" is guest of group "Owned"
- When I visit dashboard groups page
- Then I should see group "Owned" in group list
- Then I should see group "Guest" in group list
- When I click on the "Leave" button for group "Owned"
- Then I should see the "Can not leave message"
-
- @javascript
- Scenario: Guest should be able to leave from group
- Given "Mary Jane" is guest of group "Guest"
- When I visit dashboard groups page
- Then I should see group "Owned" in group list
- Then I should see group "Guest" in group list
- When I click on the "Leave" button for group "Guest"
- When I visit dashboard groups page
- Then I should see group "Owned" in group list
- Then I should not see group "Guest" in group list
-
- @javascript
- Scenario: Guest should be able to leave from group even if he is the only user in the group
- When I visit dashboard groups page
- Then I should see group "Owned" in group list
- Then I should see group "Guest" in group list
- When I click on the "Leave" button for group "Guest"
- When I visit dashboard groups page
- Then I should see group "Owned" in group list
- Then I should not see group "Guest" in group list
-
Scenario: Create a group from dasboard
And I visit dashboard groups page
And I click new group link
And submit form with new group "Samurai" info
Then I should be redirected to group "Samurai" page
And I should see newly created group "Samurai"
-
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/active_tab.feature b/features/project/active_tab.feature
index c4f987a7923..57dda9c2234 100644
--- a/features/project/active_tab.feature
+++ b/features/project/active_tab.feature
@@ -10,9 +10,9 @@ Feature: Project Active Tab
Then the active main tab should be Home
And no other main tabs should be active
- Scenario: On Project Code
+ Scenario: On Project Repository
Given I visit my project's files page
- Then the active main tab should be Code
+ Then the active main tab should be Repository
And no other main tabs should be active
Scenario: On Project Issues
@@ -59,46 +59,46 @@ Feature: Project Active Tab
And no other sub navs should be active
And the active main tab should be Settings
- # Sub Tabs: Code
+ # Sub Tabs: Repository
- Scenario: On Project Code/Files
+ Scenario: On Project Repository/Files
Given I visit my project's files page
Then the active sub tab should be Files
And no other sub tabs should be active
- And the active main tab should be Code
+ And the active main tab should be Repository
- Scenario: On Project Code/Commits
+ Scenario: On Project Repository/Commits
Given I visit my project's commits page
Then the active sub tab should be Commits
And no other sub tabs should be active
- And the active main tab should be Code
+ And the active main tab should be Repository
- Scenario: On Project Code/Network
+ Scenario: On Project Repository/Network
Given I visit my project's network page
Then the active sub tab should be Network
And no other sub tabs should be active
- And the active main tab should be Code
+ And the active main tab should be Repository
- Scenario: On Project Code/Compare
+ Scenario: On Project Repository/Compare
Given I visit my project's commits page
And I click the "Compare" tab
Then the active sub tab should be Compare
And no other sub tabs should be active
- And the active main tab should be Code
+ And the active main tab should be Repository
- Scenario: On Project Code/Branches
+ Scenario: On Project Repository/Branches
Given I visit my project's commits page
And I click the "Branches" tab
Then the active sub tab should be Branches
And no other sub tabs should be active
- And the active main tab should be Code
+ And the active main tab should be Repository
- Scenario: On Project Code/Tags
+ Scenario: On Project Repository/Tags
Given I visit my project's commits page
And I click the "Tags" tab
Then the active sub tab should be Tags
And no other sub tabs should be active
- And the active main tab should be Code
+ And the active main tab should be Repository
Scenario: On Project Issues/Browse
Given I visit my project's issues page
diff --git a/features/project/commits/commits.feature b/features/project/commits/commits.feature
index a95df038357..8b0cb90765e 100644
--- a/features/project/commits/commits.feature
+++ b/features/project/commits/commits.feature
@@ -83,11 +83,6 @@ Feature: Project Commits
#Given I visit my project's commits stats page
#Then I see commits stats
- Scenario: I browse big commit
- Given I visit big commit page
- Then I see big commit warning
- And I see "Reload with full diff" link
-
Scenario: I browse a commit with an image
Given I visit a commit with an image that changed
Then The diff links to both the previous and current image
diff --git a/features/project/commits/diff_comments.feature b/features/project/commits/diff_comments.feature
index 2bde4c8a99b..35687aac9ea 100644
--- a/features/project/commits/diff_comments.feature
+++ b/features/project/commits/diff_comments.feature
@@ -6,10 +6,6 @@ Feature: Project Commits Diff Comments
And I visit project commit page
@javascript
- Scenario: I can access add diff comment buttons
- Then I should see add a diff comment button
-
- @javascript
Scenario: I can comment on a commit diff
Given I leave a diff comment like "Typo, please fix"
Then I should see a diff comment saying "Typo, please fix"
diff --git a/features/project/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/project/shortcuts.feature b/features/project/shortcuts.feature
index c73d0b32337..f71f69ef060 100644
--- a/features/project/shortcuts.feature
+++ b/features/project/shortcuts.feature
@@ -8,21 +8,21 @@ Feature: Project Shortcuts
@javascript
Scenario: Navigate to files tab
Given I press "g" and "f"
- Then the active main tab should be Code
+ Then the active main tab should be Repository
Then the active sub tab should be Files
@javascript
Scenario: Navigate to commits tab
Given I visit my project's files page
Given I press "g" and "c"
- Then the active main tab should be Code
+ Then the active main tab should be Repository
Then the active sub tab should be Commits
@javascript
Scenario: Navigate to network tab
Given I press "g" and "n"
Then the active sub tab should be Network
- And the active main tab should be Code
+ And the active main tab should be Repository
@javascript
Scenario: Navigate to graphs tab
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 e1f1db2872f..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)"
+ expect(page).to have_content "johndoe@gitlab.com"
+ expect(page).to have_content "Invited by"
expect(page).to have_content "Reporter"
end
end
@@ -92,12 +93,6 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps
current_group.add_reporter(user_john)
end
- step 'I remove user "John Doe" from group' do
- page.within "#user_#{user_john.id}" do
- click_link 'Remove user from group'
- end
- end
-
step 'I should not see "John Doe" in team list' do
page.within ".group-users-list" do
expect(page).not_to have_content "John Doe"
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/group.rb b/features/steps/dashboard/group.rb
index 9b79a3be49b..cf679fea530 100644
--- a/features/steps/dashboard/group.rb
+++ b/features/steps/dashboard/group.rb
@@ -4,44 +4,6 @@ class Spinach::Features::DashboardGroup < Spinach::FeatureSteps
include SharedPaths
include SharedUser
- # Leave
-
- step 'I click on the "Leave" button for group "Owned"' do
- find(:css, 'li', text: "Owner").find(:css, 'i.fa.fa-sign-out').click
- # poltergeist always confirms popups.
- end
-
- step 'I click on the "Leave" button for group "Guest"' do
- find(:css, 'li', text: "Guest").find(:css, 'i.fa.fa-sign-out').click
- # poltergeist always confirms popups.
- end
-
- step 'I should not see the "Leave" button for group "Owned"' do
- expect(find(:css, 'li', text: "Owner")).not_to have_selector(:css, 'i.fa.fa-sign-out')
- # poltergeist always confirms popups.
- end
-
- step 'I should not see the "Leave" button for groupr "Guest"' do
- expect(find(:css, 'li', text: "Guest")).not_to have_selector(:css, 'i.fa.fa-sign-out')
- # poltergeist always confirms popups.
- end
-
- step 'I should see group "Owned" in group list' do
- expect(page).to have_content("Owned")
- end
-
- step 'I should not see group "Owned" in group list' do
- expect(page).not_to have_content("Owned")
- end
-
- step 'I should see group "Guest" in group list' do
- expect(page).to have_content("Guest")
- end
-
- step 'I should not see group "Guest" in group list' do
- expect(page).not_to have_content("Guest")
- end
-
step 'I click new group link' do
click_link "New Group"
end
@@ -60,8 +22,4 @@ class Spinach::Features::DashboardGroup < Spinach::FeatureSteps
expect(page).to have_content "Samurai"
expect(page).to have_content "Tokugawa Shogunate"
end
-
- step 'I should see the "Can not leave message"' do
- expect(page).to have_content "You can not leave the \"Owned\" group."
- end
end
diff --git a/features/steps/dashboard/help.rb b/features/steps/dashboard/help.rb
index 800e869533e..9c94dc70df0 100644
--- a/features/steps/dashboard/help.rb
+++ b/features/steps/dashboard/help.rb
@@ -8,7 +8,7 @@ class Spinach::Features::DashboardHelp < Spinach::FeatureSteps
end
step 'I visit the "Rake Tasks" help page' do
- visit help_page_path("raketasks", "maintenance")
+ visit help_page_path("raketasks/maintenance")
end
step 'I should see "Rake Tasks" page markdown rendered' do
diff --git a/features/steps/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/notifications.rb b/features/steps/profile/notifications.rb
index 979f4692d5a..7e339443b75 100644
--- a/features/steps/profile/notifications.rb
+++ b/features/steps/profile/notifications.rb
@@ -15,8 +15,6 @@ class Spinach::Features::ProfileNotifications < Spinach::FeatureSteps
end
step 'I should see Notification saved message' do
- page.within '.flash-container' do
- expect(page).to have_content 'Notification settings saved'
- end
+ expect(page).to have_content 'On mention'
end
end
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/builds/artifacts.rb b/features/steps/project/builds/artifacts.rb
index 2876e8812e9..b4a32ed2e38 100644
--- a/features/steps/project/builds/artifacts.rb
+++ b/features/steps/project/builds/artifacts.rb
@@ -68,10 +68,16 @@ class Spinach::Features::ProjectBuildsArtifacts < Spinach::FeatureSteps
end
step 'download of a file extracted from build artifacts should start' do
- # this will be accelerated by Workhorse
- response_json = JSON.parse(page.body, symbolize_names: true)
- expect(response_json[:archive]).to end_with('build_artifacts.zip')
- expect(response_json[:entry]).to eq Base64.encode64('ci_artifacts.txt')
+ send_data = response_headers[Gitlab::Workhorse::SEND_DATA_HEADER]
+
+ expect(send_data).to start_with('artifacts-entry:')
+
+ base64_params = send_data.sub(/\Aartifacts\-entry:/, '')
+ params = JSON.parse(Base64.urlsafe_decode64(base64_params))
+
+ expect(params.keys).to eq(['Archive', 'Entry'])
+ expect(params['Archive']).to end_with('build_artifacts.zip')
+ expect(params['Entry']).to eq(Base64.encode64('ci_artifacts.txt'))
end
step 'I click a first row within build artifacts table' do
diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb
index 239036e431d..bea9f9d198b 100644
--- a/features/steps/project/commits/commits.rb
+++ b/features/steps/project/commits/commits.rb
@@ -125,25 +125,6 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
expect(page).to have_content 'Authors'
end
- step 'I visit big commit page' do
- # Create a temporary scope to ensure that the stub_const is removed after user
- RSpec::Mocks.with_temporary_scope do
- stub_const('Gitlab::Git::DiffCollection::DEFAULT_LIMITS', { max_lines: 1, max_files: 1 })
- visit namespace_project_commit_path(@project.namespace, @project, sample_big_commit.id)
- end
- end
-
- step 'I see big commit warning' do
- expect(page).to have_content sample_big_commit.message
- expect(page).to have_content "Too many changes"
- end
-
- step 'I see "Reload with full diff" link' do
- link = find_link('Reload with full diff')
- expect(link[:href]).to end_with('?force_show_diff=true')
- expect(link[:href]).not_to include('.html')
- end
-
step 'I visit a commit with an image that changed' do
visit namespace_project_commit_path(@project.namespace, @project, sample_image_commit.id)
end
diff --git a/features/steps/project/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/network_graph.rb b/features/steps/project/network_graph.rb
index 9b59b682676..019b3124a86 100644
--- a/features/steps/project/network_graph.rb
+++ b/features/steps/project/network_graph.rb
@@ -20,11 +20,11 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps
end
step 'page should select "master" in select box' do
- expect(page).to have_selector '.select2-chosen', text: "master"
+ expect(page).to have_selector '.dropdown-menu-toggle', text: "master"
end
step 'page should select "v1.0.0" in select box' do
- expect(page).to have_selector '.select2-chosen', text: "v1.0.0"
+ expect(page).to have_selector '.dropdown-menu-toggle', text: "v1.0.0"
end
step 'page should have "master" on graph' do
@@ -40,11 +40,19 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps
end
When 'I switch ref to "feature"' do
- select 'feature', from: 'ref'
+ first('.js-project-refs-dropdown').click
+
+ page.within '.project-refs-form' do
+ click_link 'feature'
+ end
end
When 'I switch ref to "v1.0.0"' do
- select 'v1.0.0', from: 'ref'
+ first('.js-project-refs-dropdown').click
+
+ page.within '.project-refs-form' do
+ click_link 'v1.0.0'
+ end
end
When 'click "Show only selected branch" checkbox' do
@@ -68,11 +76,11 @@ class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps
end
step 'page should select "feature" in select box' do
- expect(page).to have_selector '.select2-chosen', text: "feature"
+ expect(page).to have_selector '.dropdown-menu-toggle', text: "feature"
end
step 'page should select "v1.0.0" in select box' do
- expect(page).to have_selector '.select2-chosen', text: "v1.0.0"
+ expect(page).to have_selector '.dropdown-menu-toggle', text: "v1.0.0"
end
step 'page should have "feature" on graph' do
diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb
index 98b57e5cbfb..76fefee9254 100644
--- a/features/steps/project/project.rb
+++ b/features/steps/project/project.rb
@@ -134,8 +134,8 @@ class Spinach::Features::Project < Spinach::FeatureSteps
end
step 'I should see Notification saved message' do
- page.within '.flash-container' do
- expect(page).to have_content 'Notification settings saved'
+ page.within '#notifications-button' do
+ expect(page).to have_content 'On mention'
end
end
diff --git a/features/steps/project/project_find_file.rb b/features/steps/project/project_find_file.rb
index 47de4b91df1..b8da5e6435d 100644
--- a/features/steps/project/project_find_file.rb
+++ b/features/steps/project/project_find_file.rb
@@ -13,12 +13,12 @@ class Spinach::Features::ProjectFindFile < Spinach::FeatureSteps
end
step 'I should see "find file" page' do
- ensure_active_main_tab('Code')
+ ensure_active_main_tab('Repository')
expect(page).to have_selector('.file-finder-holder', count: 1)
end
step 'I fill in Find by path with "git"' do
- ensure_active_main_tab('Code')
+ ensure_active_main_tab('Repository')
expect(page).to have_selector('.file-finder-holder', count: 1)
end
@@ -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/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb
index 79a3ed8197e..0fe046dcbf6 100644
--- a/features/steps/project/source/browse_files.rb
+++ b/features/steps/project/source/browse_files.rb
@@ -290,15 +290,23 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step "I switch ref to 'test'" do
- select "'test'", from: 'ref'
+ first('.js-project-refs-dropdown').click
+
+ page.within '.project-refs-form' do
+ click_link 'test'
+ end
end
step "I switch ref to fix" do
- select "fix", from: 'ref'
+ first('.js-project-refs-dropdown').click
+
+ page.within '.project-refs-form' do
+ click_link 'fix'
+ end
end
step "I see the ref 'test' has been selected" do
- expect(page).to have_selector '.select2-chosen', text: "'test'"
+ expect(page).to have_selector '.dropdown-toggle-text', text: "'test'"
end
step "I visit the 'test' tree" do
diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb
index e8b1e4b4879..4df4e89f5b9 100644
--- a/features/steps/shared/diff_note.rb
+++ b/features/steps/shared/diff_note.rb
@@ -23,27 +23,26 @@ module SharedDiffNote
page.within(diff_file_selector) do
click_diff_line(sample_commit.line_code)
- page.within("form[id$='#{sample_commit.line_code}-true']") do
+ page.within("form[data-line-code='#{sample_commit.line_code}']") do
fill_in "note[note]", with: "Typo, please fix"
- find(".js-comment-button").trigger("click")
- sleep 0.05
+ find(".js-comment-button").click
end
end
end
step 'I leave a diff comment in a parallel view on the left side like "Old comment"' do
- click_parallel_diff_line(sample_commit.line_code, 'old')
- page.within("#{diff_file_selector} form[id$='#{sample_commit.line_code}-true']") do
+ click_parallel_diff_line(sample_commit.del_line_code, 'old')
+ page.within("#{diff_file_selector} form[data-line-code='#{sample_commit.del_line_code}']") do
fill_in "note[note]", with: "Old comment"
- find(".js-comment-button").trigger("click")
+ find(".js-comment-button").click
end
end
step 'I leave a diff comment in a parallel view on the right side like "New comment"' do
click_parallel_diff_line(sample_commit.line_code, 'new')
- page.within("#{diff_file_selector} form[id$='#{sample_commit.line_code}-true']") do
+ page.within("#{diff_file_selector} form[data-line-code='#{sample_commit.line_code}']") do
fill_in "note[note]", with: "New comment"
- find(".js-comment-button").trigger("click")
+ find(".js-comment-button").click
end
end
@@ -51,7 +50,7 @@ module SharedDiffNote
page.within(diff_file_selector) do
click_diff_line(sample_commit.line_code)
- page.within("form[id$='#{sample_commit.line_code}-true']") do
+ page.within("form[data-line-code='#{sample_commit.line_code}']") do
fill_in "note[note]", with: "Should fix it :smile:"
find('.js-md-preview-button').click
end
@@ -62,7 +61,7 @@ module SharedDiffNote
page.within(diff_file_selector) do
click_diff_line(sample_commit.del_line_code)
- page.within("form[id$='#{sample_commit.del_line_code}-true']") do
+ page.within("form[data-line-code='#{sample_commit.del_line_code}']") do
fill_in "note[note]", with: "DRY this up"
find('.js-md-preview-button').click
end
@@ -91,7 +90,7 @@ module SharedDiffNote
page.within(diff_file_selector) do
click_diff_line(sample_commit.line_code)
- page.within("form[id$='#{sample_commit.line_code}-true']") do
+ page.within("form[data-line-code='#{sample_commit.line_code}']") do
fill_in 'note[note]', with: ':smile:'
click_button('Comment')
end
@@ -165,10 +164,6 @@ module SharedDiffNote
end
end
- step 'I should see add a diff comment button' do
- expect(page).to have_css('.js-add-diff-note-button')
- end
-
step 'I should see an empty diff comment form' do
page.within(diff_file_selector) do
expect(page).to have_field("note[note]", with: "")
@@ -215,7 +210,7 @@ module SharedDiffNote
end
step 'I click side-by-side diff button' do
- find('#parallel-diff-btn').trigger('click')
+ find('#parallel-diff-btn').click
end
step 'I see side-by-side diff button' do
@@ -227,10 +222,12 @@ module SharedDiffNote
end
def click_diff_line(code)
- find("button[data-line-code='#{code}']").trigger('click')
+ find(".line_holder[id='#{code}'] td:nth-of-type(1)").trigger 'mouseover'
+ find(".line_holder[id='#{code}'] button").trigger 'click'
end
def click_parallel_diff_line(code, line_type)
- find("button[data-line-code='#{code}'][data-line-type='#{line_type}']").trigger('click')
+ find(".line_content.parallel.#{line_type}[data-line-code='#{code}']").trigger 'mouseover'
+ find(".line_holder.parallel button[data-line-code='#{code}']").trigger 'click'
end
end
diff --git a/features/steps/shared/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/shared/project_tab.rb b/features/steps/shared/project_tab.rb
index bfee8793301..d6024212601 100644
--- a/features/steps/shared/project_tab.rb
+++ b/features/steps/shared/project_tab.rb
@@ -8,8 +8,8 @@ module SharedProjectTab
ensure_active_main_tab('Project')
end
- step 'the active main tab should be Code' do
- ensure_active_main_tab('Code')
+ step 'the active main tab should be Repository' do
+ ensure_active_main_tab('Repository')
end
step 'the active main tab should be Graphs' do
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..f0a3dd8d2d0 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'
@@ -18,7 +13,7 @@ require_relative 'rerun'
if ENV['CI']
require 'knapsack'
- Knapsack::Adapters::RSpecAdapter.bind
+ Knapsack::Adapters::SpinachAdapter.bind
end
%w(select2_helper test_env repo_helpers).each do |f|
diff --git a/fixtures/emojis/aliases.json b/fixtures/emojis/aliases.json
index d3831d8045b..e2f47db0de2 100644
--- a/fixtures/emojis/aliases.json
+++ b/fixtures/emojis/aliases.json
@@ -1,16 +1,9 @@
{
- "northeast_pointing_airplane":"airplane_northeast",
"small_airplane":"airplane_small",
- "up_pointing_small_airplane":"airplane_small_up",
- "up_pointing_airplane":"airplane_up",
- "left_anger_bubble":"anger_left",
"right_anger_bubble":"anger_right",
"keycap_asterisk":"asterisk",
"atom_symbol":"atom",
"ballot_box_with_ballot":"ballot_box",
- "ballot_box_with_bold_check":"ballot_box_check",
- "ballot_box_with_script_x":"ballot_box_x",
- "ballot_script_x":"ballot_x",
"person_with_ball":"basketball_player",
"person_with_ball_tone1":"basketball_player_tone1",
"person_with_ball_tone2":"basketball_player_tone2",
@@ -21,51 +14,65 @@
"umbrella_on_ground":"beach_umbrella",
"bellhop_bell":"bellhop",
"biohazard_sign":"biohazard",
- "bouquet_of_flowers":"bouquet2",
"archery":"bow_and_arrow",
- "bullhorn_with_sound_waves":"bullhorn_waves",
- "pocket calculator":"calculator",
+ "boxing_gloves":"boxing_glove",
"spiral_calendar_pad":"calendar_spiral",
+ "call_me_hand":"call_me",
+ "call_me_hand_tone1":"call_me_tone1",
+ "call_me_hand_tone2":"call_me_tone2",
+ "call_me_hand_tone3":"call_me_tone3",
+ "call_me_hand_tone4":"call_me_tone4",
+ "call_me_hand_tone5":"call_me_tone5",
+ "kayak":"canoe",
"card_file_box":"card_box",
- "tape_cartridge":"cartridge",
+ "person_doing_cartwheel":"cartwheel",
+ "person_doing_cartwheel_tone1":"cartwheel_tone1",
+ "person_doing_cartwheel_tone2":"cartwheel_tone2",
+ "person_doing_cartwheel_tone3":"cartwheel_tone3",
+ "person_doing_cartwheel_tone4":"cartwheel_tone4",
+ "person_doing_cartwheel_tone5":"cartwheel_tone5",
"bottle_with_popping_cork":"champagne",
+ "clinking_glass":"champagne_glass",
"cheese_wedge":"cheese",
"city_sunrise":"city_sunset",
"mantlepiece_clock":"clock",
- "clockwise_right_and_left_semicircle_arrows":"clockwise_arrows",
"cloud_with_lightning":"cloud_lightning",
"cloud_with_rain":"cloud_rain",
"cloud_with_snow":"cloud_snow",
"cloud_with_tornado":"cloud_tornado",
- "old_personal_computer":"computer_old",
- "building_construction":"contruction_site",
+ "clown_face":"clown",
+ "building_construction":"construction_site",
"couch_and_lamp":"couch",
"couple_with_heart_mm":"couple_mm",
"couple_with_heart_ww":"couple_ww",
+ "face_with_cowboy_hat":"cowboy",
"lower_left_crayon":"crayon",
"cricket_bat_ball":"cricket",
"latin_cross":"cross",
- "heavy_latin_cross":"cross_heavy",
- "white_latin_cross":"cross_white",
- "black_skull_and_crossbones":"crossbones",
"passenger_ship":"cruise_ship",
"dagger_knife":"dagger",
"desktop_computer":"desktop",
"card_index_dividers":"dividers",
- "document_with_text":"document_text",
"dove_of_peace":"dove",
+ "drool":"drooling_face",
+ "drum_with_drumsticks":"drum",
"email":"e-mail",
- "back_of_envelope":"envelope_back",
- "flying_envelope":"envelope_flying",
- "stamped_envelope":"envelope_stamped",
- "pen_over_stamped_envelope":"envelope_stamped_pen",
- "white_down_pointing_left_hand_index":"finger_pointing_down",
- "sideways_white_down_pointing_index":"finger_pointing_down2",
- "sideways_white_left_pointing_index":"finger_pointing_left",
- "sideways_white_right_pointing_index":"finger_pointing_right",
- "sideways_white_up_pointing_index":"finger_pointing_up",
+ "eject_symbol":"eject",
+ "facepalm":"face_palm",
+ "facepalm_tone1":"face_palm_tone1",
+ "facepalm_tone2":"face_palm_tone2",
+ "facepalm_tone3":"face_palm_tone3",
+ "facepalm_tone4":"face_palm_tone4",
+ "facepalm_tone5":"face_palm_tone5",
+ "fencing":"fencer",
+ "hand_with_index_and_middle_finger_crossed":"fingers_crossed",
+ "hand_with_index_and_middle_fingers_crossed_tone1":"fingers_crossed_tone1",
+ "hand_with_index_and_middle_fingers_crossed_tone2":"fingers_crossed_tone2",
+ "hand_with_index_and_middle_fingers_crossed_tone3":"fingers_crossed_tone3",
+ "hand_with_index_and_middle_fingers_crossed_tone4":"fingers_crossed_tone4",
+ "hand_with_index_and_middle_fingers_crossed_tone5":"fingers_crossed_tone5",
"flame":"fire",
- "oncoming_fire_engine":"fire_engine_oncoming",
+ "first_place_medal":"first_place",
"ac":"flag_ac",
"ad":"flag_ad",
"ae":"flag_ae",
@@ -326,44 +333,51 @@
"za":"flag_za",
"zm":"flag_zm",
"zw":"flag_zw",
- "clamshell_mobile_phone":"flip_phone",
- "black_hard_shell_floppy_disk":"floppy_black",
- "white_hard_shell_floppy_disk":"floppy_white",
- "open_folder":"folder_open",
"fork_and_knife_with_plate":"fork_knife_plate",
+ "fox_face":"fox",
"frame_with_picture":"frame_photo",
- "frame_with_tiles":"frame_tiles",
- "frame_with_an_x":"frame_x",
+ "baguette_bread":"french_bread",
"anguished":"frowning",
"white_frowning_face":"frowning2",
+ "goal_net":"goal",
"hammer_and_pick":"hammer_pick",
"raised_hand_with_fingers_splayed":"hand_splayed",
- "reversed_raised_hand_with_fingers_splayed":"hand_splayed_reverse",
"raised_hand_with_fingers_splayed_tone1":"hand_splayed_tone1",
"raised_hand_with_fingers_splayed_tone2":"hand_splayed_tone2",
"raised_hand_with_fingers_splayed_tone3":"hand_splayed_tone3",
"raised_hand_with_fingers_splayed_tone4":"hand_splayed_tone4",
"raised_hand_with_fingers_splayed_tone5":"hand_splayed_tone5",
- "reversed_victory_hand":"hand_victory",
+ "shaking_hands":"handshake",
+ "shaking_hands_tone1":"handshake_tone1",
+ "shaking_hands_tone2":"handshake_tone2",
+ "shaking_hands_tone3":"handshake_tone3",
+ "shaking_hands_tone4":"handshake_tone4",
+ "shaking_hands_tone5":"handshake_tone5",
"face_with_head_bandage":"head_bandage",
"heavy_heart_exclamation_mark_ornament":"heart_exclamation",
- "heart_with_tip_on_the_left":"heart_tip",
"helmet_with_white_cross":"helmet_with_cross",
"house_buildings":"homes",
"hot_dog":"hotdog",
"derelict_house_building":"house_abandoned",
"hugging_face":"hugging",
- "circled_information_source":"info",
"desert_island":"island",
- "up_pointing_military_airplane":"jet_up",
+ "juggler":"juggling",
+ "juggler_tone1":"juggling_tone1",
+ "juggler_tone2":"juggling_tone2",
+ "juggler_tone3":"juggling_tone3",
+ "juggler_tone4":"juggling_tone4",
+ "juggler_tone5":"juggling_tone5",
"old_key":"key2",
- "wired_keyboard":"keyboard",
- "keyboard_and_mouse":"keyboard_mouse",
- "musical_keyboard_with_jacks":"keyboard_with_jacks",
"couplekiss_mm":"kiss_mm",
"couplekiss_ww":"kiss_ww",
+ "kiwifruit":"kiwi",
"satisfied":"laughing",
- "left_hand_telephone_receiver":"left_receiver",
+ "left_fist":"left_facing_fist",
+ "left_fist_tone1":"left_facing_fist_tone1",
+ "left_fist_tone2":"left_facing_fist_tone2",
+ "left_fist_tone3":"left_facing_fist_tone3",
+ "left_fist_tone4":"left_facing_fist_tone4",
+ "left_fist_tone5":"left_facing_fist_tone5",
"man_in_business_suit_levitating":"levitate",
"weight_lifter":"lifter",
"weight_lifter_tone1":"lifter_tone1",
@@ -371,9 +385,21 @@
"weight_lifter_tone3":"lifter_tone3",
"weight_lifter_tone4":"lifter_tone4",
"weight_lifter_tone5":"lifter_tone5",
- "light_mark":"light_check_mark",
"lion":"lion_face",
+ "liar":"lying_face",
+ "male_dancer":"man_dancing",
+ "male_dancer_tone1":"man_dancing_tone1",
+ "male_dancer_tone2":"man_dancing_tone2",
+ "male_dancer_tone3":"man_dancing_tone3",
+ "male_dancer_tone4":"man_dancing_tone4",
+ "male_dancer_tone5":"man_dancing_tone5",
+ "tuxedo_tone1":"man_in_tuxedo_tone1",
+ "tuxedo_tone2":"man_in_tuxedo_tone2",
+ "tuxedo_tone3":"man_in_tuxedo_tone3",
+ "tuxedo_tone4":"man_in_tuxedo_tone4",
+ "tuxedo_tone5":"man_in_tuxedo_tone5",
"world_map":"map",
+ "karate_uniform":"martial_arts_uniform",
"sports_medal":"medal",
"sign_of_the_horns":"metal",
"sign_of_the_horns_tone1":"metal_tone1",
@@ -388,21 +414,23 @@
"reversed_hand_with_middle_finger_extended_tone3":"middle_finger_tone3",
"reversed_hand_with_middle_finger_extended_tone4":"middle_finger_tone4",
"reversed_hand_with_middle_finger_extended_tone5":"middle_finger_tone5",
+ "glass_of_milk":"milk",
"money_mouth_face":"money_mouth",
- "lightning_mood_bubble":"mood_bubble_lightning",
- "lightning_mood":"mood_lightning",
+ "motorbike":"motor_scooter",
"racing_motorcycle":"motorcycle",
"snow_capped_mountain":"mountain_snow",
- "one_button_mouse":"mouse_one",
"three_button_mouse":"mouse_three_button",
+ "mother_christmas":"mrs_claus",
+ "mother_christmas_tone1":"mrs_claus_tone1",
+ "mother_christmas_tone2":"mrs_claus_tone2",
+ "mother_christmas_tone3":"mrs_claus_tone3",
+ "mother_christmas_tone4":"mrs_claus_tone4",
+ "mother_christmas_tone5":"mrs_claus_tone5",
+ "sick":"nauseated_face",
"nerd_face":"nerd",
- "three_networked_computers":"network",
"rolled_up_newspaper":"newspaper2",
- "note_page":"note",
- "empty_note_page":"note_empty",
- "note_pad":"notepad",
- "empty_note_pad":"notepad_empty",
"spiral_note_pad":"notepad_spiral",
+ "stop_sign":"octagonal_sign",
"oil_drum":"oil",
"grandma":"older_woman",
"grandma_tone1":"older_woman_tone1",
@@ -410,57 +438,66 @@
"grandma_tone3":"older_woman_tone3",
"grandma_tone4":"older_woman_tone4",
"grandma_tone5":"older_woman_tone5",
- "optical_disc_icon":"optical_disk",
"lower_left_paintbrush":"paintbrush",
"linked_paperclips":"paperclips",
"national_park":"park",
"double_vertical_bar":"pause_button",
"peace_symbol":"peace",
+ "shelled_peanut":"peanuts",
"lower_left_ballpoint_pen":"pen_ballpoint",
"lower_left_fountain_pen":"pen_fountain",
"memo":"pencil",
- "lower_left_pencil":"pencil3",
- "black_pennant":"pennant_black",
- "white_pennant":"pennant_white",
"table_tennis":"ping_pong",
- "no_piracy":"piracy",
"worship_symbol":"place_of_worship",
"shit":"poop",
"hankey":"poop",
"poo":"poop",
- "prohibited_sign":"prohibited",
+ "expecting_woman":"pregnant_woman",
+ "expecting_woman_tone1":"pregnant_woman_tone1",
+ "expecting_woman_tone2":"pregnant_woman_tone2",
+ "expecting_woman_tone3":"pregnant_woman_tone3",
+ "expecting_woman_tone4":"pregnant_woman_tone4",
+ "expecting_woman_tone5":"pregnant_woman_tone5",
"film_projector":"projector",
"racing_car":"race_car",
"radioactive_sign":"radioactive",
"railroad_track":"railway_track",
- "right_speaker_with_one_sound_wave":"right_speaker_one",
- "right_speaker_with_three_sound_waves":"right_speaker_three",
+ "back_of_hand":"raised_back_of_hand",
+ "back_of_hand_tone1":"raised_back_of_hand_tone1",
+ "back_of_hand_tone2":"raised_back_of_hand_tone2",
+ "back_of_hand_tone3":"raised_back_of_hand_tone3",
+ "back_of_hand_tone4":"raised_back_of_hand_tone4",
+ "back_of_hand_tone5":"raised_back_of_hand_tone5",
+ "rhinoceros":"rhino",
+ "right_fist":"right_facing_fist",
+ "right_fist_tone1":"right_facing_fist_tone1",
+ "right_fist_tone2":"right_facing_fist_tone2",
+ "right_fist_tone3":"right_facing_fist_tone3",
+ "right_fist_tone4":"right_facing_fist_tone4",
+ "right_fist_tone5":"right_facing_fist_tone5",
"robot_face":"robot",
+ "rolling_on_the_floor_laughing":"rofl",
"face_with_rolling_eyes":"rolling_eyes",
+ "green_salad":"salad",
+ "second_place_medal":"second_place",
+ "paella":"shallow_pan_of_food",
+ "shopping_trolley":"shopping_cart",
"skeleton":"skull",
"skull_and_crossbones":"skull_crossbones",
"slightly_frowning_face":"slight_frown",
"slightly_smiling_face":"slight_smile",
+ "sneeze":"sneezing_face",
"speaking_head_in_silhouette":"speaking_head",
- "left_speech_bubble":"speech_left",
- "right_speech_bubble":"speech_right",
- "three_speech_bubbles":"speech_three",
- "two_speech_bubbles":"speech_two",
"sleuth_or_spy":"spy",
"sleuth_or_spy_tone1":"spy_tone1",
"sleuth_or_spy_tone2":"spy_tone2",
"sleuth_or_spy_tone3":"spy_tone3",
"sleuth_or_spy_tone4":"spy_tone4",
"sleuth_or_spy_tone5":"spy_tone5",
- "portable_stereo":"stereo",
- "black_touchtone_telephone":"telephone_black",
- "white_touchtone_telephone":"telephone_white",
+ "stuffed_pita":"stuffed_flatbread",
"face_with_thermometer":"thermometer_face",
"thinking_face":"thinking",
- "left_thought_bubble":"thought_left",
- "right_thought_bubble":"thought_right",
- "reversed_thumbs_down_sign":"thumbs_down_reverse",
- "reversed_thumbs_up_sign":"thumbs_up_reverse",
+ "third_place_medal":"third_place",
"-1":"thumbsdown",
"-1_tone1":"thumbsdown_tone1",
"-1_tone2":"thumbsdown_tone2",
@@ -479,9 +516,7 @@
"hammer_and_wrench":"tools",
"next_track":"track_next",
"previous_track":"track_previous",
- "diesel_locomotive":"train_diesel",
- "triangle_with_rounded_corners":"triangle_round",
- "turned_ok_hand_sign":"turned_ok_hand",
+ "whisky":"tumbler_glass",
"unicorn_face":"unicorn",
"upside_down_face":"upside_down",
"funeral_urn":"urn",
@@ -494,6 +529,12 @@
"white_sun_behind_cloud":"white_sun_cloud",
"white_sun_behind_cloud_with_rain":"white_sun_rain_cloud",
"white_sun_with_small_cloud":"white_sun_small_cloud",
- "left_writing_hand":"writing_hand",
+ "wilted_flower":"wilted_rose",
+ "wrestling":"wrestlers",
+ "wrestling_tone1":"wrestlers_tone1",
+ "wrestling_tone2":"wrestlers_tone2",
+ "wrestling_tone3":"wrestlers_tone3",
+ "wrestling_tone4":"wrestlers_tone4",
+ "wrestling_tone5":"wrestlers_tone5",
"zipper_mouth_face":"zipper_mouth"
}
diff --git a/fixtures/emojis/digests.json b/fixtures/emojis/digests.json
index 41ca617847e..078d3413f33 100644
--- a/fixtures/emojis/digests.json
+++ b/fixtures/emojis/digests.json
@@ -2,757 +2,697 @@
{
"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"
- },
- {
- "name": "airplane_northeast",
- "unicode": "1F6EA",
- "digest": "fdddc2cd3618ec6661612581b8b93553cb086b0bb197e96aedf1bee8055e7bb4"
- },
- {
- "name": "northeast_pointing_airplane",
- "unicode": "1F6EA",
- "digest": "fdddc2cd3618ec6661612581b8b93553cb086b0bb197e96aedf1bee8055e7bb4"
+ "digest": "5544eace06b8e1b6ea91940e893e013d33d6b166e14e6d128a87f2cd2de88332"
},
{
"name": "airplane_small",
"unicode": "1F6E9",
- "digest": "f98b44422d6bf505b50330805ecf68013d035341f0b6487c3c05ad913eb5abd3"
+ "digest": "1a2e07abbbe90d05cee7ff8dd52f443d595ccb38959f3089fe016b77e5d6de7d"
},
{
"name": "small_airplane",
"unicode": "1F6E9",
- "digest": "f98b44422d6bf505b50330805ecf68013d035341f0b6487c3c05ad913eb5abd3"
- },
- {
- "name": "airplane_small_up",
- "unicode": "1F6E8",
- "digest": "029752b29a757c087dec60f45ea242e974fc181129e20390d5d4a2f90442091a"
- },
- {
- "name": "up_pointing_small_airplane",
- "unicode": "1F6E8",
- "digest": "029752b29a757c087dec60f45ea242e974fc181129e20390d5d4a2f90442091a"
- },
- {
- "name": "airplane_up",
- "unicode": "1F6E7",
- "digest": "ec45d4dbfce1f75dc59339417b1dcf5f1e1359cd9d04ff233babf359a3330e77"
- },
- {
- "name": "up_pointing_airplane",
- "unicode": "1F6E7",
- "digest": "ec45d4dbfce1f75dc59339417b1dcf5f1e1359cd9d04ff233babf359a3330e77"
+ "digest": "1a2e07abbbe90d05cee7ff8dd52f443d595ccb38959f3089fe016b77e5d6de7d"
},
{
"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"
- },
- {
- "name": "anger_left",
- "unicode": "1F5EE",
- "digest": "f2711991e8b386b2d5b12f296ce20a9b4b00ef91d6d67af2cf4e06abf2faa1dc"
- },
- {
- "name": "left_anger_bubble",
- "unicode": "1F5EE",
- "digest": "f2711991e8b386b2d5b12f296ce20a9b4b00ef91d6d67af2cf4e06abf2faa1dc"
+ "digest": "332493913891aa0eda2743b4bb16c4682400f249998bf34eb292246c9009e17f"
},
{
"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"
- },
- {
- "name": "ascending_notes",
- "unicode": "1F39C",
- "digest": "33432042771d456338dda5d98e49322d3600f2cc9049963480c7c38d9de1ef0a"
+ "digest": "c115e6613ebd718268aa31d265e017138b9fb58bbb8201eb3f40de2380e460aa"
},
{
"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": "avocado",
+ "unicode": "1F951",
+ "digest": "bc1fb203d63b18985598400925de24050bb192afda1cbf0813f85cb139869eff"
},
{
"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": "bacon",
+ "unicode": "1F953",
+ "digest": "18ad3817f1f88a69706db5727a58e763dde6c21a2d4f184c3d728c32dc5fa05a"
},
{
"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"
- },
- {
- "name": "ballot_box_check",
- "unicode": "1F5F9",
- "digest": "fc3ba16c009d963a4a0ea20a348ac98eee3c4c18c481df19a5ada0d1de7fcc15"
- },
- {
- "name": "ballot_box_with_bold_check",
- "unicode": "1F5F9",
- "digest": "fc3ba16c009d963a4a0ea20a348ac98eee3c4c18c481df19a5ada0d1de7fcc15"
+ "digest": "4175a56eca5c6458574a681e109b1403fbb143cf27f69ae6c1917650f3e08892"
},
{
"name": "ballot_box_with_check",
"unicode": "2611",
- "digest": "5f5cec7fe462557d31e8d2b836534c1e76d546cc0061236fa2af3667972b84aa"
- },
- {
- "name": "ballot_box_x",
- "unicode": "1F5F5",
- "digest": "861dcfc2361298262587b5d0e163fed96a55c44636361f5b4a9ab1d6502b8928"
- },
- {
- "name": "ballot_box_with_script_x",
- "unicode": "1F5F5",
- "digest": "861dcfc2361298262587b5d0e163fed96a55c44636361f5b4a9ab1d6502b8928"
- },
- {
- "name": "ballot_x",
- "unicode": "1F5F4",
- "digest": "0b73b89847eb82bcad5664644c8af237e0aef6c3d8c94b7a5df94e05d0ebf4e1"
- },
- {
- "name": "ballot_script_x",
- "unicode": "1F5F4",
- "digest": "0b73b89847eb82bcad5664644c8af237e0aef6c3d8c94b7a5df94e05d0ebf4e1"
+ "digest": "c98d6f3588dd87e2f318bbfe6c646399a905450edfd814edae4e5b1bddef2134"
},
{
"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": "bat",
+ "unicode": "1F987",
+ "digest": "8fc19e0d7d6f80906bdbc06d616a810de66180d96cf28070a53fa61b88904535"
},
{
"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",
@@ -760,2374 +700,2484 @@
"digest": "c2ba672994ad0f99d7fdc449f3fee45a2dca68a58f9fe95825b38465a30ef44e"
},
{
+ "name": "black_heart",
+ "unicode": "1F5A4",
+ "digest": "f334679168d6dd7328c28e9ae3cb2b1fca0e9c2777938d586bfe623db2a688b9"
+ },
+ {
"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"
- },
- {
- "name": "book2",
- "unicode": "1F56E",
- "digest": "26d6b66a1957e7750b3e22eb2e46d0cc85932977bbb81d3d8482ec1ec58ee12b"
+ "digest": "9d912a9d1bb10dc7f2645b345ed09e90461e83df0de275acb806f1f75cef1fcf"
},
{
"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"
- },
- {
- "name": "bouquet2",
- "unicode": "1F395",
- "digest": "1643ec51ff26fc1ac0c67859e202386398650bf2a996c82b68e1b73fa52abf7d"
- },
- {
- "name": "bouquet_of_flowers",
- "unicode": "1F395",
- "digest": "1643ec51ff26fc1ac0c67859e202386398650bf2a996c82b68e1b73fa52abf7d"
+ "digest": "b93751a27b40f6185a22b3e8b413f0fe09b6010d1057c672e1a23088e0b8286f"
},
{
"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": "boxing_glove",
+ "unicode": "1F94A",
+ "digest": "c914b2ce45f20afad66ad6f0d1b0750c4469e4f48b686dfc4aad1ec8d289c563"
+ },
+ {
+ "name": "boxing_gloves",
+ "unicode": "1F94A",
+ "digest": "c914b2ce45f20afad66ad6f0d1b0750c4469e4f48b686dfc4aad1ec8d289c563"
},
{
"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"
- },
- {
- "name": "boys_symbol",
- "unicode": "1F6C9",
- "digest": "47fadbcb876ca436264ce2f3ebd1472bd68f55cc2b4833bf054335be9dc7a0f2"
+ "digest": "0f76e97237203950da36c737dcc6f56dcd6c123401a8c817a0636376c7f38ef5"
},
{
"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"
- },
- {
- "name": "bullhorn",
- "unicode": "1F56B",
- "digest": "a4ca5cbfe299e8ccd148d17055d2d395cf8515e416bf771044c9a670509a8254"
- },
- {
- "name": "bullhorn_waves",
- "unicode": "1F56C",
- "digest": "92493636cf086205d1e12cc19e613b84152ef10b8cd0215619a0fc813bfc9a7c"
- },
- {
- "name": "bullhorn_with_sound_waves",
- "unicode": "1F56C",
- "digest": "92493636cf086205d1e12cc19e613b84152ef10b8cd0215619a0fc813bfc9a7c"
+ "digest": "96e74842e919716b7bbbab57339bfd70f099a9bcb4710dffd7c80cf38a7bbff7"
},
{
"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": "butterfly",
+ "unicode": "1F98B",
+ "digest": "a91b6598c17b44a8dc8935a1d99e25f4483ea41470cdd2da343039a9eec29ef1"
},
{
"name": "cactus",
"unicode": "1F335",
- "digest": "e87588e6548d201db903dc0523b3ccc83c6b559981d743eae1504ce668cd8be4"
+ "digest": "2c5c4c35f26c7046fdc002b337e0d939729b33a26980e675950f9934c91e40fd"
},
{
"name": "cake",
"unicode": "1F370",
- "digest": "3947783d128018f5e396602d0492cb5c31e8e8df98af01eda7cade71aea8d989"
- },
- {
- "name": "calculator",
- "unicode": "1F5A9",
- "digest": "01b47b5c69c12b65fa4f4c0d580f2a98280d6116f4ad2cf8be378759008bcc3c"
- },
- {
- "name": "pocket calculator",
- "unicode": "1F5A9",
- "digest": "01b47b5c69c12b65fa4f4c0d580f2a98280d6116f4ad2cf8be378759008bcc3c"
+ "digest": "b928902df8084210d51c1da36f9119164a325393c391b28cd8ea914e0b95c17b"
},
{
"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": "call_me",
+ "unicode": "1F919",
+ "digest": "83d2ed96dcb8b4adf4f4d030ffd07e25ca16351e1a4fbefdf9f46f5ca496a55f"
+ },
+ {
+ "name": "call_me_hand",
+ "unicode": "1F919",
+ "digest": "83d2ed96dcb8b4adf4f4d030ffd07e25ca16351e1a4fbefdf9f46f5ca496a55f"
+ },
+ {
+ "name": "call_me_tone1",
+ "unicode": "1F919-1F3FB",
+ "digest": "4a5748efa83e7294e8338b8795d4d315ff1cd31ead6759004d0eb330e50de8cd"
+ },
+ {
+ "name": "call_me_hand_tone1",
+ "unicode": "1F919-1F3FB",
+ "digest": "4a5748efa83e7294e8338b8795d4d315ff1cd31ead6759004d0eb330e50de8cd"
+ },
+ {
+ "name": "call_me_tone2",
+ "unicode": "1F919-1F3FC",
+ "digest": "54feaa6e3c5789ae6e15622127f0e0213234b4b886e1588ce95814348b1f1519"
+ },
+ {
+ "name": "call_me_hand_tone2",
+ "unicode": "1F919-1F3FC",
+ "digest": "54feaa6e3c5789ae6e15622127f0e0213234b4b886e1588ce95814348b1f1519"
+ },
+ {
+ "name": "call_me_tone3",
+ "unicode": "1F919-1F3FD",
+ "digest": "57e949b951e14843b712dab5a828f915ee255f5bb973db33946aab4057427419"
+ },
+ {
+ "name": "call_me_hand_tone3",
+ "unicode": "1F919-1F3FD",
+ "digest": "57e949b951e14843b712dab5a828f915ee255f5bb973db33946aab4057427419"
+ },
+ {
+ "name": "call_me_tone4",
+ "unicode": "1F919-1F3FE",
+ "digest": "f7787e933978a09c7b8ab8d3b1e1ab395aaae998c455e93bb3db24a4c8a60fe0"
+ },
+ {
+ "name": "call_me_hand_tone4",
+ "unicode": "1F919-1F3FE",
+ "digest": "f7787e933978a09c7b8ab8d3b1e1ab395aaae998c455e93bb3db24a4c8a60fe0"
+ },
+ {
+ "name": "call_me_tone5",
+ "unicode": "1F919-1F3FF",
+ "digest": "1fdb7d833d000b117d20d48142d3026a61cc9c8b712ebb498fa66bf75c74d7a5"
+ },
+ {
+ "name": "call_me_hand_tone5",
+ "unicode": "1F919-1F3FF",
+ "digest": "1fdb7d833d000b117d20d48142d3026a61cc9c8b712ebb498fa66bf75c74d7a5"
},
{
"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"
- },
- {
- "name": "cancellation_x",
- "unicode": "1F5D9",
- "digest": "cea2f7a48543207615ee06755ded62c2a95a7eaf7d7b68a3fc25e74d94e2c92c"
+ "digest": "a42a4ff9521affa72db7b0f01da169b4cb6afb9db1c5dfad47dd4c507bfc30d9"
},
{
"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": "canoe",
+ "unicode": "1F6F6",
+ "digest": "56ca308cc2ad4827468cf58c4ccf6ef6b3382835a91e935540a2b973e01d2572"
+ },
+ {
+ "name": "kayak",
+ "unicode": "1F6F6",
+ "digest": "56ca308cc2ad4827468cf58c4ccf6ef6b3382835a91e935540a2b973e01d2572"
},
{
"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": "carrot",
+ "unicode": "1F955",
+ "digest": "3a6fd98b63ee73d982a9cdacb08cf7b4014368cde8ffce6056b7df25a5a472b1"
+ },
+ {
+ "name": "cartwheel",
+ "unicode": "1F938",
+ "digest": "d78de3435e0b04a9b1a1048ae12e63e3248f9ace3a0db4d3bda584f22af18863"
+ },
+ {
+ "name": "person_doing_cartwheel",
+ "unicode": "1F938",
+ "digest": "d78de3435e0b04a9b1a1048ae12e63e3248f9ace3a0db4d3bda584f22af18863"
},
{
- "name": "cartridge",
- "unicode": "1F5AD",
- "digest": "0b1625eea118060b51a70905c1eb3313ed632e989f70943eca16aa29fe8a34f2"
+ "name": "cartwheel_tone1",
+ "unicode": "1F938-1F3FB",
+ "digest": "39a49781a269bb40d8efc8fd73c973b00fb2e192850ea6073062b5dea0cd5b74"
},
{
- "name": "tape_cartridge",
- "unicode": "1F5AD",
- "digest": "0b1625eea118060b51a70905c1eb3313ed632e989f70943eca16aa29fe8a34f2"
+ "name": "person_doing_cartwheel_tone1",
+ "unicode": "1F938-1F3FB",
+ "digest": "39a49781a269bb40d8efc8fd73c973b00fb2e192850ea6073062b5dea0cd5b74"
+ },
+ {
+ "name": "cartwheel_tone2",
+ "unicode": "1F938-1F3FC",
+ "digest": "6231eb35be45457fd648f8f4b79983f03705c9d983a18067f7e6d9ae47bc1958"
+ },
+ {
+ "name": "person_doing_cartwheel_tone2",
+ "unicode": "1F938-1F3FC",
+ "digest": "6231eb35be45457fd648f8f4b79983f03705c9d983a18067f7e6d9ae47bc1958"
+ },
+ {
+ "name": "cartwheel_tone3",
+ "unicode": "1F938-1F3FD",
+ "digest": "ca483c78cc823811a8c279c501d9b283e4c990dafc5995ad40e68ecb0af554df"
+ },
+ {
+ "name": "person_doing_cartwheel_tone3",
+ "unicode": "1F938-1F3FD",
+ "digest": "ca483c78cc823811a8c279c501d9b283e4c990dafc5995ad40e68ecb0af554df"
+ },
+ {
+ "name": "cartwheel_tone4",
+ "unicode": "1F938-1F3FE",
+ "digest": "8253afb672431c84e498014c30babb00b9284bec773009e79f7f06aa7108643e"
+ },
+ {
+ "name": "person_doing_cartwheel_tone4",
+ "unicode": "1F938-1F3FE",
+ "digest": "8253afb672431c84e498014c30babb00b9284bec773009e79f7f06aa7108643e"
+ },
+ {
+ "name": "cartwheel_tone5",
+ "unicode": "1F938-1F3FF",
+ "digest": "6fd92baff57c38b3adb6753d9e7e547e762971a8872fd3f1e71c6aaf0b1d3ab9"
+ },
+ {
+ "name": "person_doing_cartwheel_tone5",
+ "unicode": "1F938-1F3FF",
+ "digest": "6fd92baff57c38b3adb6753d9e7e547e762971a8872fd3f1e71c6aaf0b1d3ab9"
},
{
"name": "cat",
"unicode": "1F431",
- "digest": "002208c0c9165971853ee05cd05513175a913376a462a345a939d73401c6acb7"
+ "digest": "e52d0d3a205a0ba99094717e171a7f572b713a0e21b276ffa4a826596fe5cafc"
},
{
"name": "cat2",
"unicode": "1F408",
- "digest": "fbdb726cc035f83784dcfe2d9adb85f8aeec429064aed5c5ca0b8be406068aa5"
+ "digest": "46aa67a99f782935932c77b8de93287142297abe52928c173191cf55bb8f4339"
},
{
"name": "cd",
"unicode": "1F4BF",
- "digest": "bd4d4eef2cc0b1e4ee1f5280f922743e76f27d35836987801b2b48969eac17d8"
- },
- {
- "name": "celtic_cross",
- "unicode": "1F548",
- "digest": "187aac988d7e02085a15f31c4cc0ff25127be5b088e354e65c7b1152bffb40ff"
+ "digest": "16363d8a34b873c12df6354b99f575cae3d80e0d27100ed7eea70f0310953c7b"
},
{
"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": "champagne_glass",
+ "unicode": "1F942",
+ "digest": "5a2e4773f7eb126a00122cbfa4dc535da51ce00e0bf0d8d6ff8bab8b3365f8d2"
+ },
+ {
+ "name": "clinking_glass",
+ "unicode": "1F942",
+ "digest": "5a2e4773f7eb126a00122cbfa4dc535da51ce00e0bf0d8d6ff8bab8b3365f8d2"
},
{
"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"
- },
- {
- "name": "clockwise_arrows",
- "unicode": "1F5D8",
- "digest": "67027b7e1a4d800a3ce7d731c21c098d1109d217159a27665eebb7e080fc2622"
- },
- {
- "name": "clockwise_right_and_left_semicircle_arrows",
- "unicode": "1F5D8",
- "digest": "67027b7e1a4d800a3ce7d731c21c098d1109d217159a27665eebb7e080fc2622"
+ "digest": "9fdef6a4939315c017b165e1dbac7710fb335df8c309be3fe2a011ef7fc28d74"
},
{
"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": "clown",
+ "unicode": "1F921",
+ "digest": "eea95687caabc9e808514c2450ba599e5e24ef47923dbec86f5297a64438e2e5"
+ },
+ {
+ "name": "clown_face",
+ "unicode": "1F921",
+ "digest": "eea95687caabc9e808514c2450ba599e5e24ef47923dbec86f5297a64438e2e5"
},
{
"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"
- },
- {
- "name": "computer_old",
- "unicode": "1F5B3",
- "digest": "b27c30d74f205a8a3bd00a55ca17da7cf6ae3b65ae33e949755a4c6bd69a9fd3"
- },
- {
- "name": "old_personal_computer",
- "unicode": "1F5B3",
- "digest": "b27c30d74f205a8a3bd00a55ca17da7cf6ae3b65ae33e949755a4c6bd69a9fd3"
+ "digest": "c970ce76b5607434895b0407bdaa93140f887930781a17dd7dcf16f711451d93"
},
{
"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": "building_construction",
+ "unicode": "1F3D7",
+ "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"
- },
- {
- "name": "contruction_site",
- "unicode": "1F3D7",
- "digest": "0ff52e6adf1927d356b27be5fef6bad2ad842be05e3a0bd16a17efe78e5676d9"
- },
- {
- "name": "building_construction",
- "unicode": "1F3D7",
- "digest": "0ff52e6adf1927d356b27be5fef6bad2ad842be05e3a0bd16a17efe78e5676d9"
+ "digest": "0d7f33ff7acc1cc3a81e6a786ff007df20da145e3070f338505dfed5100e9fcb"
},
{
"name": "convenience_store",
"unicode": "1F3EA",
- "digest": "1ff4351e4a4503f58ed5d35074a2112c681337e35ffe55332187481685573606"
+ "digest": "975dcf9b8e9e3fb1e29574b41300b9d96fd64703b3c18ff52f9f1875d1cf1b52"
},
{
"name": "cookie",
"unicode": "1F36A",
- "digest": "5c78ce2e721b0a3767d6ce0b59c1e88fdf94a7edc94e98c4d6b7aadb5b2aeea7"
+ "digest": "4bed3522bd50091ac5b68ca760661eb484d7f1b9c9d564d2097bd812b7f28ae4"
+ },
+ {
+ "name": "cooking",
+ "unicode": "1F373",
+ "digest": "563ffd6cae381ce1e318cdacc54e70040d6a01a50d0db8aeb50edbbe413eac58"
},
{
"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": "cowboy",
+ "unicode": "1F920",
+ "digest": "1aabf23f6b95a9b772fdb8eb45b8ec93584a5357f9131c6eabc9d1b83fe67e89"
+ },
+ {
+ "name": "face_with_cowboy_hat",
+ "unicode": "1F920",
+ "digest": "1aabf23f6b95a9b772fdb8eb45b8ec93584a5357f9131c6eabc9d1b83fe67e89"
},
{
"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": "croissant",
+ "unicode": "1F950",
+ "digest": "b751e287157a1e276617a841a5b5f7f1208ca226cfd8fa947f144390b65a5e16"
},
{
"name": "cross",
"unicode": "271D",
- "digest": "a6e3c345cf6aa2ce690b66454066b53ef5b1dab2ed635e21f1586b1dffc5df42"
+ "digest": "a6b07c838fb75ef2ebefa2df6005e8d784753239ec03c37695a13e3b1954d653"
},
{
"name": "latin_cross",
"unicode": "271D",
- "digest": "a6e3c345cf6aa2ce690b66454066b53ef5b1dab2ed635e21f1586b1dffc5df42"
- },
- {
- "name": "cross_heavy",
- "unicode": "1F547",
- "digest": "2e37c26b9bad0beb019c7f3e7a3892352d0ad9ca1b90c4333d42e8d56680be70"
- },
- {
- "name": "heavy_latin_cross",
- "unicode": "1F547",
- "digest": "2e37c26b9bad0beb019c7f3e7a3892352d0ad9ca1b90c4333d42e8d56680be70"
- },
- {
- "name": "cross_white",
- "unicode": "1F546",
- "digest": "3452e667010d7e49a51d7e1f4ba8ed4f303e33ed43255a051e9a18832a1efba6"
- },
- {
- "name": "white_latin_cross",
- "unicode": "1F546",
- "digest": "3452e667010d7e49a51d7e1f4ba8ed4f303e33ed43255a051e9a18832a1efba6"
- },
- {
- "name": "crossbones",
- "unicode": "1F571",
- "digest": "f5e7ce293c1a3282711073e68f033a3876e8428d1218cb2f8294630f9124e584"
- },
- {
- "name": "black_skull_and_crossbones",
- "unicode": "1F571",
- "digest": "f5e7ce293c1a3282711073e68f033a3876e8428d1218cb2f8294630f9124e584"
+ "digest": "a6b07c838fb75ef2ebefa2df6005e8d784753239ec03c37695a13e3b1954d653"
},
{
"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": "cucumber",
+ "unicode": "1F952",
+ "digest": "d1196e23f2f155ef5c1330f8497f40957a7357cb177127f457c5c471f0a23727"
},
{
"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"
+ "name": "deer",
+ "unicode": "1F98C",
+ "digest": "7f4302ca68fd121ee73be48d0a0a0fb9e7e2741071a491ad2b7b0eab9f11ad25"
},
{
- "name": "descending_notes",
- "unicode": "1F39D",
- "digest": "f09c6a2e094b13bf91cc07b7b776e43348ccef9f91247ca36cc02e7d91098af0"
+ "name": "department_store",
+ "unicode": "1F3EC",
+ "digest": "4be910d2efe74d8ce2c1f41d7753c8873579faca83fcf779a4887d8ab9e5923b"
},
{
"name": "desert",
"unicode": "1F3DC",
- "digest": "e45815250bfc5411de516f87efa218874bcda4b0420b4c17182efc22ba0ce80d"
+ "digest": "d4b1a11c5130debe042df6cc2b3389f15c68a5cb32dc1b3a82b78f733d0c9e4e"
},
{
"name": "desktop",
"unicode": "1F5A5",
- "digest": "ba46323e695918e7253f1013cb991efb09790581c74c07c38bc5e10a20b8e8de"
+ "digest": "cde5bfb6c71bb7d663808a3561b24cb5b5560f95f510b40f81250cac1b21933e"
},
{
"name": "desktop_computer",
"unicode": "1F5A5",
- "digest": "ba46323e695918e7253f1013cb991efb09790581c74c07c38bc5e10a20b8e8de"
- },
- {
- "name": "desktop_window",
- "unicode": "1F5D4",
- "digest": "d5b6c4a847e2a96f97f50fd353a22cb121915cb1d7bbc0f02df38769819b6b7e"
+ "digest": "cde5bfb6c71bb7d663808a3561b24cb5b5560f95f510b40f81250cac1b21933e"
},
{
"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"
- },
- {
- "name": "document",
- "unicode": "1F5CE",
- "digest": "2cbca96cc69306a10f1a9b6505723e027239439d899f6b395dc43f3c37d2d777"
- },
- {
- "name": "document_text",
- "unicode": "1F5B9",
- "digest": "29407b12409c9673f3d89ef1f86ee50cbc7ed53b1870e33b4a29bbc609017f72"
- },
- {
- "name": "document_with_text",
- "unicode": "1F5B9",
- "digest": "29407b12409c9673f3d89ef1f86ee50cbc7ed53b1870e33b4a29bbc609017f72"
+ "digest": "98b07fbbcdb438d1b8a755869fa2de8e180a77fce359ec830eb46d38ec3e67cb"
},
{
"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": "drooling_face",
+ "unicode": "1F924",
+ "digest": "5203cb05cd266d7a7c929ab40364ad68571d380d9c7ff93a8d6d55261abaa1ba"
+ },
+ {
+ "name": "drool",
+ "unicode": "1F924",
+ "digest": "5203cb05cd266d7a7c929ab40364ad68571d380d9c7ff93a8d6d55261abaa1ba"
},
{
"name": "droplet",
"unicode": "1F4A7",
- "digest": "a92c419792cbd3ba90ed21547362134cfac3e17a5304ee4e3872c9f7b561f834"
+ "digest": "6475b4a4460a672c436a68f282ac97fb31e2934db4b80620063ee816159aa7c3"
+ },
+ {
+ "name": "drum",
+ "unicode": "1F941",
+ "digest": "0d0639980b1a5dcbf1c3e7ef47263fb6543b871242c58452a8c2f642525d9dd8"
+ },
+ {
+ "name": "drum_with_drumsticks",
+ "unicode": "1F941",
+ "digest": "0d0639980b1a5dcbf1c3e7ef47263fb6543b871242c58452a8c2f642525d9dd8"
+ },
+ {
+ "name": "duck",
+ "unicode": "1F986",
+ "digest": "8f8373798a7727368b32328e7a9a349727a949e7391ddd243b6456141a4f7e94"
},
{
"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": "eagle",
+ "unicode": "1F985",
+ "digest": "b44fd4f61b83c5114358a272343ac9b0eabbc70847f739bbdbf8aae3ade5bc1d"
},
{
"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"
+ "unicode": "1F95A",
+ "digest": "72b9c841af784e7cbccbbe48ba833df5cecdd284397c199cab079872e879d92f"
},
{
"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": "eject",
+ "unicode": "23CF",
+ "digest": "eeb0cd23ead0c965e307de517a6805265f0c780c3e454e64bc4c1425dfe7548e"
+ },
+ {
+ "name": "eject_symbol",
+ "unicode": "23CF",
+ "digest": "eeb0cd23ead0c965e307de517a6805265f0c780c3e454e64bc4c1425dfe7548e"
},
{
"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"
- },
- {
- "name": "envelope_back",
- "unicode": "1F582",
- "digest": "bc60b6d375feee00758a94a05b42eeb165f4084b20eb3e6012b72faa221f7e75"
- },
- {
- "name": "back_of_envelope",
- "unicode": "1F582",
- "digest": "bc60b6d375feee00758a94a05b42eeb165f4084b20eb3e6012b72faa221f7e75"
- },
- {
- "name": "envelope_flying",
- "unicode": "1F585",
- "digest": "9d6b6ca4c08006062a6f11948de3e15b13cf5c458967e39a9358665a8e13e214"
- },
- {
- "name": "flying_envelope",
- "unicode": "1F585",
- "digest": "9d6b6ca4c08006062a6f11948de3e15b13cf5c458967e39a9358665a8e13e214"
- },
- {
- "name": "envelope_stamped",
- "unicode": "1F583",
- "digest": "f6102aea7283ddc136bfeb09589573420b9279105045fc6b965c1633c1297468"
- },
- {
- "name": "stamped_envelope",
- "unicode": "1F583",
- "digest": "f6102aea7283ddc136bfeb09589573420b9279105045fc6b965c1633c1297468"
- },
- {
- "name": "envelope_stamped_pen",
- "unicode": "1F586",
- "digest": "80ea471318d1e04f8e525ff236b3cd4a4c864e66c6246b6aad77d92f56895f33"
- },
- {
- "name": "pen_over_stamped_envelope",
- "unicode": "1F586",
- "digest": "80ea471318d1e04f8e525ff236b3cd4a4c864e66c6246b6aad77d92f56895f33"
+ "digest": "f5a512022a2f5280f372ff39c22cbda815f698710ca66f8f8c4d08418f98ca78"
},
{
"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": "face_palm",
+ "unicode": "1F926",
+ "digest": "4ec873048b34b1bb34430724cf28e4bee6c0a9eee88ce39b9d1565047dc92420"
+ },
+ {
+ "name": "face_palm_tone1",
+ "unicode": "1F926-1F3FB",
+ "digest": "e93ef92b4c01dbea6c400e708e23dd36da92ccfbf5eb4f177b3b20c3a46bdc19"
+ },
+ {
+ "name": "face_palm_tone2",
+ "unicode": "1F926-1F3FC",
+ "digest": "22c8bf9fd9fa2ed9dca7a6397ed00ba6cfe9aeef2b0fb7b516ee4dda0df050ea"
+ },
+ {
+ "name": "face_palm_tone3",
+ "unicode": "1F926-1F3FD",
+ "digest": "c0b8bb9d2423e6787b6bdf1ca5a13f52853e4f48a9a1af0f2d4af1364fff022e"
+ },
+ {
+ "name": "face_palm_tone4",
+ "unicode": "1F926-1F3FE",
+ "digest": "f522ab186adcbb4549ea2c03500cdd7a86add548e43ebf7a54d58cc24deea072"
+ },
+ {
+ "name": "face_palm_tone5",
+ "unicode": "1F926-1F3FF",
+ "digest": "363507ae7178b5ec583635f47bcab10c897346f48b85d8759b1004c32cd8ad65"
},
{
"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": "fencer",
+ "unicode": "1F93A",
+ "digest": "5db00fa456af9f6c7cb88d300579dd63e426bcb97ad25486b664aff25c688e21"
+ },
+ {
+ "name": "fencing",
+ "unicode": "1F93A",
+ "digest": "5db00fa456af9f6c7cb88d300579dd63e426bcb97ad25486b664aff25c688e21"
},
{
"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": "fingers_crossed",
+ "unicode": "1F91E",
+ "digest": "a5c797ead191b9712e185083266b455cdf09f6a34c10f8c51aa145e6073427e1"
},
{
- "name": "finger_pointing_down",
- "unicode": "1F597",
- "digest": "0c542ac3141e8f2e74767acd0eb399c2d68c779cb78bf16d437ad3b1f8134ad9"
+ "name": "hand_with_index_and_middle_finger_crossed",
+ "unicode": "1F91E",
+ "digest": "a5c797ead191b9712e185083266b455cdf09f6a34c10f8c51aa145e6073427e1"
},
{
- "name": "white_down_pointing_left_hand_index",
- "unicode": "1F597",
- "digest": "0c542ac3141e8f2e74767acd0eb399c2d68c779cb78bf16d437ad3b1f8134ad9"
+ "name": "fingers_crossed_tone1",
+ "unicode": "1F91E-1F3FB",
+ "digest": "db56d47bf887f2d8459a3aaba23f15c0087234ae5a54125052e7046e034a4988"
},
{
- "name": "finger_pointing_down2",
- "unicode": "1F59F",
- "digest": "c5b128a232cbf518544802a2ae1459368274297163721fa05d0103cf95b2b1ee"
+ "name": "hand_with_index_and_middle_fingers_crossed_tone1",
+ "unicode": "1F91E-1F3FB",
+ "digest": "db56d47bf887f2d8459a3aaba23f15c0087234ae5a54125052e7046e034a4988"
},
{
- "name": "sideways_white_down_pointing_index",
- "unicode": "1F59F",
- "digest": "c5b128a232cbf518544802a2ae1459368274297163721fa05d0103cf95b2b1ee"
+ "name": "fingers_crossed_tone2",
+ "unicode": "1F91E-1F3FC",
+ "digest": "19f1bcca3991db7ed2037278c0baab6cd7f12aeaf2e0074de402c4d9e45c1899"
},
{
- "name": "finger_pointing_left",
- "unicode": "1F598",
- "digest": "d178ece691e2091be08db77fda9cf05462934628557358a8cb6222587b291f7e"
+ "name": "hand_with_index_and_middle_fingers_crossed_tone2",
+ "unicode": "1F91E-1F3FC",
+ "digest": "19f1bcca3991db7ed2037278c0baab6cd7f12aeaf2e0074de402c4d9e45c1899"
},
{
- "name": "sideways_white_left_pointing_index",
- "unicode": "1F598",
- "digest": "d178ece691e2091be08db77fda9cf05462934628557358a8cb6222587b291f7e"
+ "name": "fingers_crossed_tone3",
+ "unicode": "1F91E-1F3FD",
+ "digest": "895a3314f6a310f31f7e728bcca20ff834fbfac62ce00e27e3ea5ad0dfc1ba35"
},
{
- "name": "finger_pointing_right",
- "unicode": "1F599",
- "digest": "a412a47544d8f401f9181f8826c5fa3d6b42a1d76f6926963c2d9cd2a01be06d"
+ "name": "hand_with_index_and_middle_fingers_crossed_tone3",
+ "unicode": "1F91E-1F3FD",
+ "digest": "895a3314f6a310f31f7e728bcca20ff834fbfac62ce00e27e3ea5ad0dfc1ba35"
},
{
- "name": "sideways_white_right_pointing_index",
- "unicode": "1F599",
- "digest": "a412a47544d8f401f9181f8826c5fa3d6b42a1d76f6926963c2d9cd2a01be06d"
+ "name": "fingers_crossed_tone4",
+ "unicode": "1F91E-1F3FE",
+ "digest": "fcb5c4de2001d23a5df1b8702624d134b7f94e93e2dcc8adf6c1033c77722b0e"
},
{
- "name": "finger_pointing_up",
- "unicode": "1F59E",
- "digest": "32c2ccab52aa318a47c816d1bcf9c076e667c9ef3e64ce37d7ba7e827238690d"
+ "name": "hand_with_index_and_middle_fingers_crossed_tone4",
+ "unicode": "1F91E-1F3FE",
+ "digest": "fcb5c4de2001d23a5df1b8702624d134b7f94e93e2dcc8adf6c1033c77722b0e"
},
{
- "name": "sideways_white_up_pointing_index",
- "unicode": "1F59E",
- "digest": "32c2ccab52aa318a47c816d1bcf9c076e667c9ef3e64ce37d7ba7e827238690d"
+ "name": "fingers_crossed_tone5",
+ "unicode": "1F91E-1F3FF",
+ "digest": "50132c78d530b048c21be4e788b446872a79b3b3a91009db12f4021c44c8469d"
+ },
+ {
+ "name": "hand_with_index_and_middle_fingers_crossed_tone5",
+ "unicode": "1F91E-1F3FF",
+ "digest": "50132c78d530b048c21be4e788b446872a79b3b3a91009db12f4021c44c8469d"
},
{
"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",
- "unicode": "1F6F1",
- "digest": "e2482c450136d373f74dfafddf502e0b675eb5d2e1e1c645f163db0e4d15fbb6"
+ "name": "fireworks",
+ "unicode": "1F386",
+ "digest": "b62ae08a00c0cc6eba8f9666c8fd9946ce57c3cfc01fe99542a8690a4a566a65"
},
{
- "name": "oncoming_fire_engine",
- "unicode": "1F6F1",
- "digest": "e2482c450136d373f74dfafddf502e0b675eb5d2e1e1c645f163db0e4d15fbb6"
+ "name": "first_place",
+ "unicode": "1F947",
+ "digest": "e3de5d9f14f05544dbee5965cc2baa20e7b417a488c8a18598979038860fd901"
},
{
- "name": "fireworks",
- "unicode": "1F386",
- "digest": "3dee83a27c406960253ca1460eb88a599c7b81506051b69605a421b17fe8282c"
+ "name": "first_place_medal",
+ "unicode": "1F947",
+ "digest": "e3de5d9f14f05544dbee5965cc2baa20e7b417a488c8a18598979038860fd901"
},
{
"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,5692 +3192,6022 @@
{
"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"
- },
- {
- "name": "flip_phone",
- "unicode": "1F581",
- "digest": "be59efba4bc0759af5a726c06619090ef5071bf2541611d71691dedecee6c697"
- },
- {
- "name": "clamshell_mobile_phone",
- "unicode": "1F581",
- "digest": "be59efba4bc0759af5a726c06619090ef5071bf2541611d71691dedecee6c697"
- },
- {
- "name": "floppy_black",
- "unicode": "1F5AA",
- "digest": "9022f51bb09c5130c6d46bb2accb159bed6f54d6fbffda6ecad62965ebc958ea"
- },
- {
- "name": "black_hard_shell_floppy_disk",
- "unicode": "1F5AA",
- "digest": "9022f51bb09c5130c6d46bb2accb159bed6f54d6fbffda6ecad62965ebc958ea"
+ "digest": "ebf49007f367dc05580e9dab942e93e9dda12fa1dc2caa410ac7f8d8cd55d2a3"
},
{
"name": "floppy_disk",
"unicode": "1F4BE",
- "digest": "e987961ca516032a90942ef6c398836f2da68a5981714bd172acfe7b0e369d0a"
- },
- {
- "name": "floppy_white",
- "unicode": "1F5AB",
- "digest": "ec79c400117c4506ef8cf3eebef6c42dd37e60b3079d3e98b6ccd06e517e2af0"
- },
- {
- "name": "white_hard_shell_floppy_disk",
- "unicode": "1F5AB",
- "digest": "ec79c400117c4506ef8cf3eebef6c42dd37e60b3079d3e98b6ccd06e517e2af0"
+ "digest": "4ee0b5bba41b9e301ed125d3ee1c263bef171ca499e6e1b89276b09af2bc03a0"
},
{
"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"
- },
- {
- "name": "folder",
- "unicode": "1F5C0",
- "digest": "8932141321911032ce8469ba85fe309b78384545c3b9946978b383670b956644"
- },
- {
- "name": "folder_open",
- "unicode": "1F5C1",
- "digest": "74f3b484771c3d6ef61cf003de25c1a59b875afa46c057b5b1d92d9f99460685"
- },
- {
- "name": "open_folder",
- "unicode": "1F5C1",
- "digest": "74f3b484771c3d6ef61cf003de25c1a59b875afa46c057b5b1d92d9f99460685"
+ "digest": "bc3631a4e9e8473b92e842008937add2cd9ffad5b7d772ce759fb5ff6c0e3dca"
},
{
"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": "fox",
+ "unicode": "1F98A",
+ "digest": "e9903cb0396f7e49bdd2c384b38e614c13bfa576b3ecc1ec7b9819e4a40d91d1"
+ },
+ {
+ "name": "fox_face",
+ "unicode": "1F98A",
+ "digest": "e9903cb0396f7e49bdd2c384b38e614c13bfa576b3ecc1ec7b9819e4a40d91d1"
},
{
"name": "frame_photo",
"unicode": "1F5BC",
- "digest": "6ff21063063989c6ae7dd69f4d6a781c676f9dba380d8e6f1dbac5d53b24f349"
+ "digest": "d5074f748a15055ec1fb812c1e5e169e6e3cc73c522c54be1359b0e26c0fc75c"
},
{
"name": "frame_with_picture",
"unicode": "1F5BC",
- "digest": "6ff21063063989c6ae7dd69f4d6a781c676f9dba380d8e6f1dbac5d53b24f349"
+ "digest": "d5074f748a15055ec1fb812c1e5e169e6e3cc73c522c54be1359b0e26c0fc75c"
},
{
- "name": "frame_tiles",
- "unicode": "1F5BD",
- "digest": "34a5bb044b4b3ad94b116ad106f7b6747fb8612dc0e9f8ccd4313c2920508df0"
- },
- {
- "name": "frame_with_tiles",
- "unicode": "1F5BD",
- "digest": "34a5bb044b4b3ad94b116ad106f7b6747fb8612dc0e9f8ccd4313c2920508df0"
+ "name": "free",
+ "unicode": "1F193",
+ "digest": "9973522457158362fc5bdd7da858e6371e28a8403d1ef9e4b6427195c7f72cfa"
},
{
- "name": "frame_x",
- "unicode": "1F5BE",
- "digest": "2e427688fd70361c8c59787d0722ad68abe1c3f968258ee99c0c77ce4b8a8e15"
+ "name": "french_bread",
+ "unicode": "1F956",
+ "digest": "47518a4312f57207b8e8c38188d4a2bd8b16830a885cfcf2d281cfab50c1bc6e"
},
{
- "name": "frame_with_an_x",
- "unicode": "1F5BE",
- "digest": "2e427688fd70361c8c59787d0722ad68abe1c3f968258ee99c0c77ce4b8a8e15"
- },
- {
- "name": "free",
- "unicode": "1F193",
- "digest": "c1d9172a656717f78d941303c5da8790c6cd9827838d8f7dc3719afb53bcab80"
+ "name": "baguette_bread",
+ "unicode": "1F956",
+ "digest": "47518a4312f57207b8e8c38188d4a2bd8b16830a885cfcf2d281cfab50c1bc6e"
},
{
"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"
- },
- {
- "name": "girls_symbol",
- "unicode": "1F6CA",
- "digest": "2c55aee81defd7a1620ffeaad8d9bcc1835f19237c72c79633aec45671ddb9ff"
+ "digest": "f55e4b16a41b6f5e3c817a301420360ba4486e4e82e1092a56a3e3cc4069087d"
},
{
"name": "globe_with_meridians",
"unicode": "1F310",
- "digest": "945646de3d8f057760fe374494a253d9a6aa8a132309154b0a5bdbffb5b20c3f"
+ "digest": "725bebeb3c09a9e3701ebe49e672dcfbf2b73575e05f0821263511577b013b75"
+ },
+ {
+ "name": "goal",
+ "unicode": "1F945",
+ "digest": "7088c432f276ff6f447dc0d431b9062b394fb401de1072fe59ca56267bfd6717"
+ },
+ {
+ "name": "goal_net",
+ "unicode": "1F945",
+ "digest": "7088c432f276ff6f447dc0d431b9062b394fb401de1072fe59ca56267bfd6717"
},
{
"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": "gorilla",
+ "unicode": "1F98D",
+ "digest": "4a564dc14f8ae5450d094f6410ec7f099a7f07dc5254b6395f44a35527bdb4b7"
},
{
"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"
- },
- {
- "name": "hand_splayed_reverse",
- "unicode": "1F591",
- "digest": "ff0af0fe9def7388adca6836e5958492282b1afae99f1b6e1e65d11ba68b96db"
- },
- {
- "name": "reversed_raised_hand_with_fingers_splayed",
- "unicode": "1F591",
- "digest": "ff0af0fe9def7388adca6836e5958492282b1afae99f1b6e1e65d11ba68b96db"
+ "digest": "c51a30cb7e575d29ffed16780a6c95ae3f300b8ac523012f4a6e116d68c1fd15"
},
{
"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",
- "unicode": "1F594",
- "digest": "2d512ced4e8a438f2a346aed67310d3080f9828c748ade1be95943c32ba1c735"
+ "name": "handbag",
+ "unicode": "1F45C",
+ "digest": "45410a3eed0c2e3f68748d7649fa9e33a90f4e80d5291206bdd0b40380c6da45"
},
{
- "name": "reversed_victory_hand",
- "unicode": "1F594",
- "digest": "2d512ced4e8a438f2a346aed67310d3080f9828c748ade1be95943c32ba1c735"
+ "name": "handball",
+ "unicode": "1F93E",
+ "digest": "94ceb28024eb3259d8b137cafd7438773e717fbc04f5da810f85e43ca0fa9e00"
},
{
- "name": "handbag",
- "unicode": "1F45C",
- "digest": "f1e2822c67f659b52c76821dd9db001332215a8566fc1846c89b6019c9758038"
+ "name": "handball_tone1",
+ "unicode": "1F93E-1F3FB",
+ "digest": "8bec4de0d05c80e335e44d65598d186ca92696977353c9fd9c2a5efa122cb842"
+ },
+ {
+ "name": "handball_tone2",
+ "unicode": "1F93E-1F3FC",
+ "digest": "2ff4131e1e2f089b315d8e176c9348877c26c2bd03706fb75d41bc61bc99bf93"
+ },
+ {
+ "name": "handball_tone3",
+ "unicode": "1F93E-1F3FD",
+ "digest": "224a71f94dd37d3729325d11412334667a81422e21f6d7c008730ff350f51a80"
+ },
+ {
+ "name": "handball_tone4",
+ "unicode": "1F93E-1F3FE",
+ "digest": "a5f7a9db790565981bad2d0d9e09554c8c509a8179b4705a418300d58a7894b4"
+ },
+ {
+ "name": "handball_tone5",
+ "unicode": "1F93E-1F3FF",
+ "digest": "00404572d4683f2e8e8a494aa733e96fbec1723634d0a8cb8d75f2829a789d27"
+ },
+ {
+ "name": "handshake",
+ "unicode": "1F91D",
+ "digest": "cb4b08b70560908f96bda0aecd2f4c966bea180f9b7200e4c81d342dc8d36087"
+ },
+ {
+ "name": "shaking_hands",
+ "unicode": "1F91D",
+ "digest": "cb4b08b70560908f96bda0aecd2f4c966bea180f9b7200e4c81d342dc8d36087"
+ },
+ {
+ "name": "handshake_tone1",
+ "unicode": "1F91D-1F3FB",
+ "digest": "40470e224683ba375ed8698c0cbd560556be5a8898237ddf504377a3a7e89ff0"
+ },
+ {
+ "name": "shaking_hands_tone1",
+ "unicode": "1F91D-1F3FB",
+ "digest": "40470e224683ba375ed8698c0cbd560556be5a8898237ddf504377a3a7e89ff0"
+ },
+ {
+ "name": "handshake_tone2",
+ "unicode": "1F91D-1F3FC",
+ "digest": "77ed378243bf682f1f4f1a8caeabcbedf772f54631cc40ea46c099e46a499b18"
+ },
+ {
+ "name": "shaking_hands_tone2",
+ "unicode": "1F91D-1F3FC",
+ "digest": "77ed378243bf682f1f4f1a8caeabcbedf772f54631cc40ea46c099e46a499b18"
},
{
- "name": "hard_disk",
- "unicode": "1F5B4",
- "digest": "df8549d4281f5ae70fb6792a02c078e651764b0276aa43b7407236bd38fc21b4"
+ "name": "handshake_tone3",
+ "unicode": "1F91D-1F3FD",
+ "digest": "81b95050f0878b617f5d2640e34031c26a0072e46ca5a688eb4356e48bc74c92"
+ },
+ {
+ "name": "shaking_hands_tone3",
+ "unicode": "1F91D-1F3FD",
+ "digest": "81b95050f0878b617f5d2640e34031c26a0072e46ca5a688eb4356e48bc74c92"
+ },
+ {
+ "name": "handshake_tone4",
+ "unicode": "1F91D-1F3FE",
+ "digest": "74919a6f026fbbd0ccdbdbd4288d1b2ef3bda8930e9142c07736db4a7f3ef345"
+ },
+ {
+ "name": "shaking_hands_tone4",
+ "unicode": "1F91D-1F3FE",
+ "digest": "74919a6f026fbbd0ccdbdbd4288d1b2ef3bda8930e9142c07736db4a7f3ef345"
+ },
+ {
+ "name": "handshake_tone5",
+ "unicode": "1F91D-1F3FF",
+ "digest": "a30d662bfad0074ca7e32cf6f7229b643b636c4beaec496777eb7e1d5b6fc470"
+ },
+ {
+ "name": "shaking_hands_tone5",
+ "unicode": "1F91D-1F3FF",
+ "digest": "a30d662bfad0074ca7e32cf6f7229b643b636c4beaec496777eb7e1d5b6fc470"
},
{
"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"
- },
- {
- "name": "heart_tip",
- "unicode": "1F394",
- "digest": "2178829e2c85accda55d2f685544587f6de5c8398a127ae1e08ff1c4ab282204"
- },
- {
- "name": "heart_with_tip_on_the_left",
- "unicode": "1F394",
- "digest": "2178829e2c85accda55d2f685544587f6de5c8398a127ae1e08ff1c4ab282204"
+ "digest": "8a1f28b97d661ca4cff5ee13889ca61b5fa745ccb590e80832b7d7701df101d6"
},
{
"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"
- },
- {
- "name": "info",
- "unicode": "1F6C8",
- "digest": "59c35e77d5ee663c5d56f7d8af845ce8aeb9935e526ae4a06e02ae70e71212ca"
- },
- {
- "name": "circled_information_source",
- "unicode": "1F6C8",
- "digest": "59c35e77d5ee663c5d56f7d8af845ce8aeb9935e526ae4a06e02ae70e71212ca"
+ "digest": "310b7bdcca93452fe10c72c03d0aafa12b98e5d3408896d275d06d3693812c7a"
},
{
"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"
- },
- {
- "name": "jet_up",
- "unicode": "1F6E6",
- "digest": "3708e5e034b1c64d1268d66527e13c369aa0f8903bce9172bef773b2d1940948"
- },
- {
- "name": "up_pointing_military_airplane",
- "unicode": "1F6E6",
- "digest": "3708e5e034b1c64d1268d66527e13c369aa0f8903bce9172bef773b2d1940948"
+ "digest": "f986ad32e419cca81c995f8371f0189d1490172a97ebbeac60054a1af08949c5"
},
{
"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"
+ "name": "juggling",
+ "unicode": "1F939",
+ "digest": "1f5dafa78de8b37f3df88fdf3084d2380666bd74ab2f449754d8724f6f8dbfa5"
},
{
- "name": "key",
- "unicode": "1F511",
- "digest": "c68ed648350d3976c8d27a709020c8873ecf553929e66453acff96231684a1a2"
+ "name": "juggler",
+ "unicode": "1F939",
+ "digest": "1f5dafa78de8b37f3df88fdf3084d2380666bd74ab2f449754d8724f6f8dbfa5"
},
{
- "name": "key2",
- "unicode": "1F5DD",
- "digest": "87a7d42531d7a11dcb11b0d6d1be611ee8cec35b5d22226a8ac6083fedef4f5d"
+ "name": "juggling_tone1",
+ "unicode": "1F939-1F3FB",
+ "digest": "b0b4d020148c896be69c28b08e3c486f6db270d138c7ccf4be362b29eb99878d"
},
{
- "name": "old_key",
- "unicode": "1F5DD",
- "digest": "87a7d42531d7a11dcb11b0d6d1be611ee8cec35b5d22226a8ac6083fedef4f5d"
+ "name": "juggler_tone1",
+ "unicode": "1F939-1F3FB",
+ "digest": "b0b4d020148c896be69c28b08e3c486f6db270d138c7ccf4be362b29eb99878d"
},
{
- "name": "keyboard",
- "unicode": "1F5AE",
- "digest": "3b254cbf19946df3af05e501d11653d89fcda91684b7248d86186f842b83bf16"
+ "name": "juggling_tone2",
+ "unicode": "1F939-1F3FC",
+ "digest": "cfe0c1649b2fdca03673e0e64f3a7d06d4bd49b8954c769aeb7eb88b70ec99f4"
},
{
- "name": "wired_keyboard",
- "unicode": "1F5AE",
- "digest": "3b254cbf19946df3af05e501d11653d89fcda91684b7248d86186f842b83bf16"
+ "name": "juggler_tone2",
+ "unicode": "1F939-1F3FC",
+ "digest": "cfe0c1649b2fdca03673e0e64f3a7d06d4bd49b8954c769aeb7eb88b70ec99f4"
},
{
- "name": "keyboard_mouse",
- "unicode": "1F5A6",
- "digest": "95b523e55d8afeaeb06442bbe20e47f49643bb0c32d89a8cdbbccdead20532b3"
+ "name": "juggling_tone3",
+ "unicode": "1F939-1F3FD",
+ "digest": "7f87022722008bb265abe245e8157dc7a61944f5da62b3cf86f26ee1b3bdef63"
},
{
- "name": "keyboard_and_mouse",
- "unicode": "1F5A6",
- "digest": "95b523e55d8afeaeb06442bbe20e47f49643bb0c32d89a8cdbbccdead20532b3"
+ "name": "juggler_tone3",
+ "unicode": "1F939-1F3FD",
+ "digest": "7f87022722008bb265abe245e8157dc7a61944f5da62b3cf86f26ee1b3bdef63"
},
{
- "name": "keyboard_with_jacks",
- "unicode": "1F398",
- "digest": "e29a0d0b8018d13458469edca13c60a882a2817957c1aa11b050684c995a47ee"
+ "name": "juggling_tone4",
+ "unicode": "1F939-1F3FE",
+ "digest": "1f00da8c05582c95501cc6c3fe5ce0f9bfbc16789dcee59844a8fe7831198583"
},
{
- "name": "musical_keyboard_with_jacks",
- "unicode": "1F398",
- "digest": "e29a0d0b8018d13458469edca13c60a882a2817957c1aa11b050684c995a47ee"
+ "name": "juggler_tone4",
+ "unicode": "1F939-1F3FE",
+ "digest": "1f00da8c05582c95501cc6c3fe5ce0f9bfbc16789dcee59844a8fe7831198583"
},
{
- "name": "keycap_ten",
- "unicode": "1F51F",
- "digest": "7593aa7ffe7192a2e35c6ccec76522f6243777783c9152c7c03419835ea58c03"
+ "name": "juggling_tone5",
+ "unicode": "1F939-1F3FF",
+ "digest": "a195bf734788eb7961c00dbc05255a49da8b9d5042fada29b26cc20393d3ce52"
+ },
+ {
+ "name": "juggler_tone5",
+ "unicode": "1F939-1F3FF",
+ "digest": "a195bf734788eb7961c00dbc05255a49da8b9d5042fada29b26cc20393d3ce52"
+ },
+ {
+ "name": "kaaba",
+ "unicode": "1F54B",
+ "digest": "a4618782f9583f077bd383965f1c91b9985a949bb7b6cec7af22914e7f5e9ab6"
+ },
+ {
+ "name": "key",
+ "unicode": "1F511",
+ "digest": "66719fa77a50a0827c8d47237e2704c03e38186e6fef80627a765473b2294c2e"
+ },
+ {
+ "name": "key2",
+ "unicode": "1F5DD",
+ "digest": "f57240a014a9da5da3d4d98c17d0a55e0ff2e5f2d22731d2fc867105cff54c6e"
+ },
+ {
+ "name": "old_key",
+ "unicode": "1F5DD",
+ "digest": "f57240a014a9da5da3d4d98c17d0a55e0ff2e5f2d22731d2fc867105cff54c6e"
+ },
+ {
+ "name": "keyboard",
+ "unicode": "2328",
+ "digest": "34da8ff62ca964142f9281b80123dbba74deaac8d77fa61758c30cfb36c31386"
},
{
"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": "kiwi",
+ "unicode": "1F95D",
+ "digest": "70a3a05f333d9455d2da12eed970bc3baae416286848fed8e5dd31b5be0819be"
+ },
+ {
+ "name": "kiwifruit",
+ "unicode": "1F95D",
+ "digest": "70a3a05f333d9455d2da12eed970bc3baae416286848fed8e5dd31b5be0819be"
},
{
"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"
+ "name": "left_facing_fist",
+ "unicode": "1F91B",
+ "digest": "7861be485beefae0de341df2f21576666e22f63511a033e785752f30c07291da"
+ },
+ {
+ "name": "left_fist",
+ "unicode": "1F91B",
+ "digest": "7861be485beefae0de341df2f21576666e22f63511a033e785752f30c07291da"
+ },
+ {
+ "name": "left_facing_fist_tone1",
+ "unicode": "1F91B-1F3FB",
+ "digest": "2e4c4dd96b0e4b46fe0f9ce5666344d266d0f17a8544cbae73d96638d1955296"
+ },
+ {
+ "name": "left_fist_tone1",
+ "unicode": "1F91B-1F3FB",
+ "digest": "2e4c4dd96b0e4b46fe0f9ce5666344d266d0f17a8544cbae73d96638d1955296"
+ },
+ {
+ "name": "left_facing_fist_tone2",
+ "unicode": "1F91B-1F3FC",
+ "digest": "b96a63a801175ce98a75f0edad7b5574251a3fbbd894d8ab3f21aeeda366cc13"
+ },
+ {
+ "name": "left_fist_tone2",
+ "unicode": "1F91B-1F3FC",
+ "digest": "b96a63a801175ce98a75f0edad7b5574251a3fbbd894d8ab3f21aeeda366cc13"
+ },
+ {
+ "name": "left_facing_fist_tone3",
+ "unicode": "1F91B-1F3FD",
+ "digest": "99df84635513c2ebfef24df1bd3705233e02149eef788c7b82ca0548df6f6ea5"
+ },
+ {
+ "name": "left_fist_tone3",
+ "unicode": "1F91B-1F3FD",
+ "digest": "99df84635513c2ebfef24df1bd3705233e02149eef788c7b82ca0548df6f6ea5"
+ },
+ {
+ "name": "left_facing_fist_tone4",
+ "unicode": "1F91B-1F3FE",
+ "digest": "68954842ca725aec0aa39bce4aa81aad17ac30f5f298561dfa411feb07414cd3"
},
{
- "name": "left_receiver",
- "unicode": "1F57B",
- "digest": "8052e44951afee04c87296128744b5019ec783c9ed1a231f659af6c8ddaa50f3"
+ "name": "left_fist_tone4",
+ "unicode": "1F91B-1F3FE",
+ "digest": "68954842ca725aec0aa39bce4aa81aad17ac30f5f298561dfa411feb07414cd3"
},
{
- "name": "left_hand_telephone_receiver",
- "unicode": "1F57B",
- "digest": "8052e44951afee04c87296128744b5019ec783c9ed1a231f659af6c8ddaa50f3"
+ "name": "left_facing_fist_tone5",
+ "unicode": "1F91B-1F3FF",
+ "digest": "a419b33fae82612dc860ff48950c0547a1642d4f0c94b6547324440837d3bb21"
+ },
+ {
+ "name": "left_fist_tone5",
+ "unicode": "1F91B-1F3FF",
+ "digest": "a419b33fae82612dc860ff48950c0547a1642d4f0c94b6547324440837d3bb21"
+ },
+ {
+ "name": "left_luggage",
+ "unicode": "1F6C5",
+ "digest": "6625077767a51163ea20cbc299f3c13fd5ccf1b5ce365ee702ef1fef6be3dadf"
},
{
"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"
- },
- {
- "name": "light_check_mark",
- "unicode": "1F5F8",
- "digest": "7842b0df8c2b6703bed0cce5d2790d394eec7120b2a245a76f375528f2729a7b"
- },
- {
- "name": "light_mark",
- "unicode": "1F5F8",
- "digest": "7842b0df8c2b6703bed0cce5d2790d394eec7120b2a245a76f375528f2729a7b"
+ "digest": "79b0edf6ce1fd024dd7f458e322ad8588af0b789a04cc1cf38380dc8b9c76f55"
},
{
"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"
- },
- {
- "name": "lips2",
- "unicode": "1F5E2",
- "digest": "c6ba915982ac47d8aaf14ad3605949df95588acfb4e147bf608f8c1714cdf19b"
+ "digest": "8740d8086525c7a836d64625a6915cc1c59af69ba143456dbb59e0179276895e"
},
{
"name": "lipstick",
"unicode": "1F484",
- "digest": "335b912e163020df3d6d9f0a19a55d6547bd59b471c5a3e374c2968e49911ccc"
+ "digest": "751dcb22706a796033b13a2ccb94304236ec13207ad4d011e02d230ae33ab5c1"
+ },
+ {
+ "name": "lizard",
+ "unicode": "1F98E",
+ "digest": "fb9191f9eab58b8403d4c4626ccbb14ba05c1f6944011751a8edcc4dd03c66e6"
},
{
"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": "lying_face",
+ "unicode": "1F925",
+ "digest": "ce836170165e1b70938273f289c02c2106873cd9ab5472dbcd487c2f9f53f13d"
+ },
+ {
+ "name": "liar",
+ "unicode": "1F925",
+ "digest": "ce836170165e1b70938273f289c02c2106873cd9ab5472dbcd487c2f9f53f13d"
},
{
"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_dancing",
+ "unicode": "1F57A",
+ "digest": "9f632ee0c886d5f03c61e5f3a27668262c0cc2693b857a91c23c1e5ea3785b9e"
+ },
+ {
+ "name": "male_dancer",
+ "unicode": "1F57A",
+ "digest": "9f632ee0c886d5f03c61e5f3a27668262c0cc2693b857a91c23c1e5ea3785b9e"
+ },
+ {
+ "name": "man_dancing_tone1",
+ "unicode": "1F57A-1F3FB",
+ "digest": "6c56a16cb105bcdd97472645b3a351cebdbb1132cbfd18b0118f289db5fbe741"
+ },
+ {
+ "name": "male_dancer_tone1",
+ "unicode": "1F57A-1F3FB",
+ "digest": "6c56a16cb105bcdd97472645b3a351cebdbb1132cbfd18b0118f289db5fbe741"
+ },
+ {
+ "name": "man_dancing_tone2",
+ "unicode": "1F57A-1F3FC",
+ "digest": "ed7e78c14d205a03fdd5581e5213add69a55e13b4cbaf76a6d5a0d6c80f53327"
+ },
+ {
+ "name": "male_dancer_tone2",
+ "unicode": "1F57A-1F3FC",
+ "digest": "ed7e78c14d205a03fdd5581e5213add69a55e13b4cbaf76a6d5a0d6c80f53327"
+ },
+ {
+ "name": "man_dancing_tone3",
+ "unicode": "1F57A-1F3FD",
+ "digest": "13b45403e11800163406206eedeb8b579cc83eca2f60246be97e099164387bc8"
+ },
+ {
+ "name": "male_dancer_tone3",
+ "unicode": "1F57A-1F3FD",
+ "digest": "13b45403e11800163406206eedeb8b579cc83eca2f60246be97e099164387bc8"
+ },
+ {
+ "name": "man_dancing_tone4",
+ "unicode": "1F57A-1F3FE",
+ "digest": "f6feb1b0b83565fadcdd1a8737d3daa08893e919547d2a06de899160162d9c4a"
+ },
+ {
+ "name": "male_dancer_tone4",
+ "unicode": "1F57A-1F3FE",
+ "digest": "f6feb1b0b83565fadcdd1a8737d3daa08893e919547d2a06de899160162d9c4a"
+ },
+ {
+ "name": "man_dancing_tone5",
+ "unicode": "1F57A-1F3FF",
+ "digest": "fe20a9ed9ba991653b4d0683de347ed7c226a5d75610307584a2ddd6fcd1e3f2"
+ },
+ {
+ "name": "male_dancer_tone5",
+ "unicode": "1F57A-1F3FF",
+ "digest": "fe20a9ed9ba991653b4d0683de347ed7c226a5d75610307584a2ddd6fcd1e3f2"
+ },
+ {
+ "name": "man_in_tuxedo",
+ "unicode": "1F935",
+ "digest": "4d451a971dfefedc4830ba78e19b123f250e09ae65baddccdc56c0f8aa3a9b50"
+ },
+ {
+ "name": "man_in_tuxedo_tone1",
+ "unicode": "1F935-1F3FB",
+ "digest": "2814833334fb211ae2ecb1fb5964e9752282d0fb4d7f3477de5dd2a4f812a793"
+ },
+ {
+ "name": "tuxedo_tone1",
+ "unicode": "1F935-1F3FB",
+ "digest": "2814833334fb211ae2ecb1fb5964e9752282d0fb4d7f3477de5dd2a4f812a793"
+ },
+ {
+ "name": "man_in_tuxedo_tone2",
+ "unicode": "1F935-1F3FC",
+ "digest": "cd1bab9ee0e2335d3cd99d51216cccdc4fc3c2cf20129b8b7e11a51a77258f68"
+ },
+ {
+ "name": "tuxedo_tone2",
+ "unicode": "1F935-1F3FC",
+ "digest": "cd1bab9ee0e2335d3cd99d51216cccdc4fc3c2cf20129b8b7e11a51a77258f68"
+ },
+ {
+ "name": "man_in_tuxedo_tone3",
+ "unicode": "1F935-1F3FD",
+ "digest": "f387775f925fe60b9f3e7cad63a55d4d196ddd41658029a70440d14c17cb99f9"
+ },
+ {
+ "name": "tuxedo_tone3",
+ "unicode": "1F935-1F3FD",
+ "digest": "f387775f925fe60b9f3e7cad63a55d4d196ddd41658029a70440d14c17cb99f9"
+ },
+ {
+ "name": "man_in_tuxedo_tone4",
+ "unicode": "1F935-1F3FE",
+ "digest": "08debd7a573d1201aee8a2f281ef7cb638d4a2a096222150391f36963f07c622"
+ },
+ {
+ "name": "tuxedo_tone4",
+ "unicode": "1F935-1F3FE",
+ "digest": "08debd7a573d1201aee8a2f281ef7cb638d4a2a096222150391f36963f07c622"
+ },
+ {
+ "name": "man_in_tuxedo_tone5",
+ "unicode": "1F935-1F3FF",
+ "digest": "e3b10e0619f0911cf9b665a265f4ef829b8f6ba6e9c3a021d0539a27e315f8fe"
+ },
+ {
+ "name": "tuxedo_tone5",
+ "unicode": "1F935-1F3FF",
+ "digest": "e3b10e0619f0911cf9b665a265f4ef829b8f6ba6e9c3a021d0539a27e315f8fe"
},
{
"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": "martial_arts_uniform",
+ "unicode": "1F94B",
+ "digest": "a1ae797b31081425b388ab31efc635d8eb73a40980fd0fae4708aa5313e2a964"
+ },
+ {
+ "name": "karate_uniform",
+ "unicode": "1F94B",
+ "digest": "a1ae797b31081425b388ab31efc635d8eb73a40980fd0fae4708aa5313e2a964"
},
{
"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": "milk",
+ "unicode": "1F95B",
+ "digest": "38b28ea40399601fabc95bac5eaaf5a9e4e25548ec80325bd5069395ea884f85"
+ },
+ {
+ "name": "glass_of_milk",
+ "unicode": "1F95B",
+ "digest": "38b28ea40399601fabc95bac5eaaf5a9e4e25548ec80325bd5069395ea884f85"
},
{
"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"
- },
- {
- "name": "mood_bubble",
- "unicode": "1F5F0",
- "digest": "1df7061217e478d43ab9a87d4f351c4ca56705acd6b4e0b0bedfdece77635f1b"
- },
- {
- "name": "mood_bubble_lightning",
- "unicode": "1F5F1",
- "digest": "4af3e4e53eaa328b0d20542ab31705a74bf9fd368cd0673b706838ce1681d3c9"
- },
- {
- "name": "lightning_mood_bubble",
- "unicode": "1F5F1",
- "digest": "4af3e4e53eaa328b0d20542ab31705a74bf9fd368cd0673b706838ce1681d3c9"
- },
- {
- "name": "mood_lightning",
- "unicode": "1F5F2",
- "digest": "6784635e81ec722fd50a1c2a23b0f9679e4bf1b5ae2b5a01eeb995bc1f7a426f"
- },
- {
- "name": "lightning_mood",
- "unicode": "1F5F2",
- "digest": "6784635e81ec722fd50a1c2a23b0f9679e4bf1b5ae2b5a01eeb995bc1f7a426f"
+ "digest": "2c9f185babcb4001fcef2b8dfc4a32126729843084d0076c3e3ccdc845ab23ad"
},
{
"name": "mortar_board",
"unicode": "1F393",
- "digest": "cb59edb08f75c374088b65284e4d0f77b9bc9573de3e6a5127f865431011e54c"
+ "digest": "d7fbe41d4b340d3564e484aec46a22c9613521414b2ba6eece2180db4d23e410"
},
{
"name": "mosque",
"unicode": "1F54C",
- "digest": "a08ddb74342dea8f79063db6f98ba03eb08fe99481de8ce9123827ca7f17c7f3"
+ "digest": "5f3d3de7feac953a70a318113531c2857d760a516c3d8d6f42d2a3b3b67ed196"
+ },
+ {
+ "name": "motor_scooter",
+ "unicode": "1F6F5",
+ "digest": "e2dc7c981744a71f46858bd0858ff91af704ac06425ed80377bc3b119e57c872"
+ },
+ {
+ "name": "motorbike",
+ "unicode": "1F6F5",
+ "digest": "e2dc7c981744a71f46858bd0858ff91af704ac06425ed80377bc3b119e57c872"
},
{
"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"
- },
- {
- "name": "mouse_one",
- "unicode": "1F5AF",
- "digest": "e0d2055ccba489d24e0c0b6d2f22793efe48a734b0fd50f5af88f721b40665c0"
- },
- {
- "name": "one_button_mouse",
- "unicode": "1F5AF",
- "digest": "e0d2055ccba489d24e0c0b6d2f22793efe48a734b0fd50f5af88f721b40665c0"
+ "digest": "f3ed37b639b7c16aae49502bd423f9fdeabaf15bc6f0f74063954b189e176b5d"
},
{
"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": "mrs_claus",
+ "unicode": "1F936",
+ "digest": "1f72f586ca75bd7ebb4150cdcc8199a930c32fa4b81510cb8d200f1b3ddd4076"
+ },
+ {
+ "name": "mother_christmas",
+ "unicode": "1F936",
+ "digest": "1f72f586ca75bd7ebb4150cdcc8199a930c32fa4b81510cb8d200f1b3ddd4076"
+ },
+ {
+ "name": "mrs_claus_tone1",
+ "unicode": "1F936-1F3FB",
+ "digest": "244596919e0fed050203cf9e040899de323d7821235929f175852439927bd129"
+ },
+ {
+ "name": "mother_christmas_tone1",
+ "unicode": "1F936-1F3FB",
+ "digest": "244596919e0fed050203cf9e040899de323d7821235929f175852439927bd129"
+ },
+ {
+ "name": "mrs_claus_tone2",
+ "unicode": "1F936-1F3FC",
+ "digest": "8cde96e8521f3a90262a7f5f8a2989a9590d9a02cda2c37e92335dc05975c18d"
+ },
+ {
+ "name": "mother_christmas_tone2",
+ "unicode": "1F936-1F3FC",
+ "digest": "8cde96e8521f3a90262a7f5f8a2989a9590d9a02cda2c37e92335dc05975c18d"
+ },
+ {
+ "name": "mrs_claus_tone3",
+ "unicode": "1F936-1F3FD",
+ "digest": "c39cd4346d4581799dd0e9a6447c91a954a75747bf2682c8e4d79c3b0fcf7405"
+ },
+ {
+ "name": "mother_christmas_tone3",
+ "unicode": "1F936-1F3FD",
+ "digest": "c39cd4346d4581799dd0e9a6447c91a954a75747bf2682c8e4d79c3b0fcf7405"
+ },
+ {
+ "name": "mrs_claus_tone4",
+ "unicode": "1F936-1F3FE",
+ "digest": "84c85cf54559ea2d78d196fee96149a249af4f959b78e223a0ec4fb72abdbcab"
+ },
+ {
+ "name": "mother_christmas_tone4",
+ "unicode": "1F936-1F3FE",
+ "digest": "84c85cf54559ea2d78d196fee96149a249af4f959b78e223a0ec4fb72abdbcab"
+ },
+ {
+ "name": "mrs_claus_tone5",
+ "unicode": "1F936-1F3FF",
+ "digest": "ce26c0e0645713b17e7497d9f2d0484cc5477564dae99320cabf04d160d3b2ff"
+ },
+ {
+ "name": "mother_christmas_tone5",
+ "unicode": "1F936-1F3FF",
+ "digest": "ce26c0e0645713b17e7497d9f2d0484cc5477564dae99320cabf04d160d3b2ff"
},
{
"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": "nauseated_face",
+ "unicode": "1F922",
+ "digest": "f8471cf4720948d8246ec9d30e29783e819f90e3cfe8b1ba628671a1aad1a91c"
+ },
+ {
+ "name": "sick",
+ "unicode": "1F922",
+ "digest": "f8471cf4720948d8246ec9d30e29783e819f90e3cfe8b1ba628671a1aad1a91c"
},
{
"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"
- },
- {
- "name": "network",
- "unicode": "1F5A7",
- "digest": "1dbaa54deeb2328fd8a3f044e450c97ac3ff39627c598bb2f4312d677482ee06"
- },
- {
- "name": "three_networked_computers",
- "unicode": "1F5A7",
- "digest": "1dbaa54deeb2328fd8a3f044e450c97ac3ff39627c598bb2f4312d677482ee06"
+ "digest": "9e5f3c93db25cf1d0f9d6e6bd2993161afec6c30573ba3fe85e13b8c84483d66"
},
{
"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"
- },
- {
- "name": "note",
- "unicode": "1F5C9",
- "digest": "073660fdaa02ecf98d04f61f8d65d6cc447ccae3825fccaff19a2c99ebba52af"
- },
- {
- "name": "note_page",
- "unicode": "1F5C9",
- "digest": "073660fdaa02ecf98d04f61f8d65d6cc447ccae3825fccaff19a2c99ebba52af"
- },
- {
- "name": "note_empty",
- "unicode": "1F5C6",
- "digest": "06b56eeaca6349bbcf1020bea98f937450a7e086db65cd5d7497748e0fb607be"
- },
- {
- "name": "empty_note_page",
- "unicode": "1F5C6",
- "digest": "06b56eeaca6349bbcf1020bea98f937450a7e086db65cd5d7497748e0fb607be"
+ "digest": "1e0f9842e0f8ad5805eabd3f35a6038b7a2e49d566a1f5c17271f9cdf467ca60"
},
{
"name": "notebook",
"unicode": "1F4D3",
- "digest": "64bd4a3e7ca7b22fc704c7b7bd4d13540c16bc69b9d8dd76e69e6ad573ab3823"
+ "digest": "fc679d3728f86073d1607a926885dd8b0261132f5c4a0322f1e46ea9f95c8cb8"
},
{
"name": "notebook_with_decorative_cover",
"unicode": "1F4D4",
- "digest": "4b45f28fbde1be5c214a6bc2413abc91db02bccd86f74c21b7f4a4da8b75a46f"
- },
- {
- "name": "notepad",
- "unicode": "1F5CA",
- "digest": "85069e2d13540886457368a57295072aec44c7137d9223bfcf908ce1f0e5124e"
- },
- {
- "name": "note_pad",
- "unicode": "1F5CA",
- "digest": "85069e2d13540886457368a57295072aec44c7137d9223bfcf908ce1f0e5124e"
- },
- {
- "name": "notepad_empty",
- "unicode": "1F5C7",
- "digest": "8be5053e74c13d8220917c5aee1f4afdecb001612886438f283b0c2a0fecf6af"
- },
- {
- "name": "empty_note_pad",
- "unicode": "1F5C7",
- "digest": "8be5053e74c13d8220917c5aee1f4afdecb001612886438f283b0c2a0fecf6af"
+ "digest": "d822eda4b49cbfa399b36f134c1a0b8dcfdd27ed89f12c50bc18f6f0a9aa56ef"
},
{
"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": "octagonal_sign",
+ "unicode": "1F6D1",
+ "digest": "9f6927048e1f9da57f89d1ae1eb86fa4ab7abdbabca756a738a799e948d0b3f9"
+ },
+ {
+ "name": "stop_sign",
+ "unicode": "1F6D1",
+ "digest": "9f6927048e1f9da57f89d1ae1eb86fa4ab7abdbabca756a738a799e948d0b3f9"
},
{
"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"
- },
- {
- "name": "optical_disk",
- "unicode": "1F5B8",
- "digest": "df8c10028d29d65f144a6b789d1c3294e7b3293554c4c30d28d72dc7ba8d9a5d"
- },
- {
- "name": "optical_disc_icon",
- "unicode": "1F5B8",
- "digest": "df8c10028d29d65f144a6b789d1c3294e7b3293554c4c30d28d72dc7ba8d9a5d"
+ "digest": "6112e2a1656b1cb8bd9a8b0dfa6cbf66d30cae671710a9ef75c821de344aab2b"
},
{
"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": "owl",
+ "unicode": "1F989",
+ "digest": "f62ec1ad23ad9038966eea8d8b79660ac212f291af2e89bcdb0fdc683caf41e5"
},
{
"name": "ox",
"unicode": "1F402",
- "digest": "cbcfe5c8c4d6b939e24e18e610785f171bb9410441e02c2eeb1bceb0a6246daf"
+ "digest": "d13bc60552190bb9936bf32d681bdc742439b702a09cfc62137ea09a98624aed"
},
{
"name": "package",
"unicode": "1F4E6",
- "digest": "4023cffce85384217a73609f457aec013876e689c44bcfff0bcc35f3e4e1ab00"
- },
- {
- "name": "page",
- "unicode": "1F5CF",
- "digest": "cc745056525f59d9128d1d03b14770376bb09ab64b8ef4ac994ab7f38efd4783"
+ "digest": "e82bf5accebb65136e897c15607eef635fb79fd7b2d8c8e19a9eb00b6786918c"
},
{
"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"
- },
- {
- "name": "pages",
- "unicode": "1F5D0",
- "digest": "05bd47b78f089389356d9d839c736843f56b959ab4277056606ffcbb013390bc"
+ "digest": "e21c756cc1c58ebc1b37ebcd38e22a25b31e2e81306c6f18285d6a7671f9eb12"
},
{
"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": "pancakes",
+ "unicode": "1F95E",
+ "digest": "5256b4832431e8a88555796b1a9726f12d909a26fb2bdc3a0abff76412c45903"
},
{
"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": "peanuts",
+ "unicode": "1F95C",
+ "digest": "e2384846b6e4a6c3a56e991ebb749cb68b330ac00a9e9d888b2c39105ff7ff5d"
+ },
+ {
+ "name": "shelled_peanut",
+ "unicode": "1F95C",
+ "digest": "e2384846b6e4a6c3a56e991ebb749cb68b330ac00a9e9d888b2c39105ff7ff5d"
},
{
"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"
- },
- {
- "name": "pencil3",
- "unicode": "1F589",
- "digest": "52c1ba1228917eb491ac1745a495e0fdafba6b985a81caba250f71d1f94c725c"
- },
- {
- "name": "lower_left_pencil",
- "unicode": "1F589",
- "digest": "52c1ba1228917eb491ac1745a495e0fdafba6b985a81caba250f71d1f94c725c"
+ "digest": "9ca1b56b5726f472b1f1b23050ed163e213916dac379d22e38e4c8358fe871e0"
},
{
"name": "penguin",
"unicode": "1F427",
- "digest": "095de34b3f6a2521a342c21f5f2551a0092bf47429801c15b7bbf0913924f412"
- },
- {
- "name": "pennant_black",
- "unicode": "1F3F2",
- "digest": "cd3c33bfc3c7fbe84b98d2d481d56a7bf5488ff94afadd8b5a0e454768b80269"
- },
- {
- "name": "black_pennant",
- "unicode": "1F3F2",
- "digest": "cd3c33bfc3c7fbe84b98d2d481d56a7bf5488ff94afadd8b5a0e454768b80269"
- },
- {
- "name": "pennant_white",
- "unicode": "1F3F1",
- "digest": "818b1be73540f2cfeb1c514e1ee75d18715af317f0db817d9ae081b9ea33d4b0"
- },
- {
- "name": "white_pennant",
- "unicode": "1F3F1",
- "digest": "818b1be73540f2cfeb1c514e1ee75d18715af317f0db817d9ae081b9ea33d4b0"
+ "digest": "a1800ab931d6dc84a9c89bfab2c815198025c276d952509c55b18dd20bd9d316"
},
{
"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"
- },
- {
- "name": "piracy",
- "unicode": "1F572",
- "digest": "f42955ba75c598392e5e258be49968d858c876e0d6e7aa9dc795f7e8cff42be9"
- },
- {
- "name": "no_piracy",
- "unicode": "1F572",
- "digest": "f42955ba75c598392e5e258be49968d858c876e0d6e7aa9dc795f7e8cff42be9"
+ "digest": "943a858bd054c81a08a08951f8351c27c8009b85a9359729c7362868298b58e1"
},
{
"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": "potato",
+ "unicode": "1F954",
+ "digest": "a56a69f36f3a0793f278726d92c0cea2960554f3062ef1a0904526a04511d8e1"
},
{
"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": "pregnant_woman",
+ "unicode": "1F930",
+ "digest": "49abb86409103338bdb6ae43c13a78ca2dc9cd158a26df35eadd0da3c84a4352"
+ },
+ {
+ "name": "expecting_woman",
+ "unicode": "1F930",
+ "digest": "49abb86409103338bdb6ae43c13a78ca2dc9cd158a26df35eadd0da3c84a4352"
+ },
+ {
+ "name": "pregnant_woman_tone1",
+ "unicode": "1F930-1F3FB",
+ "digest": "5a9f8ed2b631ecf8af111803a5c11f4c156435a5293cb50329c7b98697c8da25"
+ },
+ {
+ "name": "expecting_woman_tone1",
+ "unicode": "1F930-1F3FB",
+ "digest": "5a9f8ed2b631ecf8af111803a5c11f4c156435a5293cb50329c7b98697c8da25"
+ },
+ {
+ "name": "pregnant_woman_tone2",
+ "unicode": "1F930-1F3FC",
+ "digest": "279a2eafff603b11629c955b05f5bd3d7da9a271d4fb3f02e9ccd457b8d2d815"
+ },
+ {
+ "name": "expecting_woman_tone2",
+ "unicode": "1F930-1F3FC",
+ "digest": "279a2eafff603b11629c955b05f5bd3d7da9a271d4fb3f02e9ccd457b8d2d815"
+ },
+ {
+ "name": "pregnant_woman_tone3",
+ "unicode": "1F930-1F3FD",
+ "digest": "93bb63ec2312db315e3f0065520b715cc413ac0fd65538ec9b5cd97df2a42b20"
+ },
+ {
+ "name": "expecting_woman_tone3",
+ "unicode": "1F930-1F3FD",
+ "digest": "93bb63ec2312db315e3f0065520b715cc413ac0fd65538ec9b5cd97df2a42b20"
+ },
+ {
+ "name": "pregnant_woman_tone4",
+ "unicode": "1F930-1F3FE",
+ "digest": "b8dc3dcec894bfd832a249459b10850f8786b6778d8887a677d1291865623da2"
+ },
+ {
+ "name": "expecting_woman_tone4",
+ "unicode": "1F930-1F3FE",
+ "digest": "b8dc3dcec894bfd832a249459b10850f8786b6778d8887a677d1291865623da2"
+ },
+ {
+ "name": "pregnant_woman_tone5",
+ "unicode": "1F930-1F3FF",
+ "digest": "73ee432752f81980f353a7f9b9f7a5ece62512dca08e15c1876b89227face21c"
+ },
+ {
+ "name": "expecting_woman_tone5",
+ "unicode": "1F930-1F3FF",
+ "digest": "73ee432752f81980f353a7f9b9f7a5ece62512dca08e15c1876b89227face21c"
+ },
+ {
+ "name": "prince",
+ "unicode": "1F934",
+ "digest": "34a0e0625f0a9825d3674192d6233b6cae4d8130451293df09f91a6a4165869c"
+ },
+ {
+ "name": "prince_tone1",
+ "unicode": "1F934-1F3FB",
+ "digest": "ccecdfeccb2ab1fceceae14f3fba875c8c7099785a4c40131c08a697b5b675fc"
+ },
+ {
+ "name": "prince_tone2",
+ "unicode": "1F934-1F3FC",
+ "digest": "c373fd3e0c1798415e3d8d88fab6c98c1bbdedcbe6f52f3a3899f6e2124a768d"
+ },
+ {
+ "name": "prince_tone3",
+ "unicode": "1F934-1F3FD",
+ "digest": "71d15695ca954d55aa69d3c753c7d31a8ba5329713a8ddbc90dafc11e524c4ef"
+ },
+ {
+ "name": "prince_tone4",
+ "unicode": "1F934-1F3FE",
+ "digest": "08f6cb32424f15cc3aaf83c31a5dac7c01a6be2f37ea8f13aed579ce6fb4db19"
+ },
+ {
+ "name": "prince_tone5",
+ "unicode": "1F934-1F3FF",
+ "digest": "77d521148efa33fa4d3409693d050fecfd948411e807327484f174e289834649"
},
{
"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"
- },
- {
- "name": "prohibited",
- "unicode": "1F6C7",
- "digest": "bc6cdea2269a0ec39576d98dc4cda2bd9efa4dc330dde870148c6a85ad9cc63f"
- },
- {
- "name": "prohibited_sign",
- "unicode": "1F6C7",
- "digest": "bc6cdea2269a0ec39576d98dc4cda2bd9efa4dc330dde870148c6a85ad9cc63f"
+ "digest": "5e5307e3dc7ec4e16c9978fb00934c99c4adefca7d32732a244d1f2de71ce6f8"
},
{
"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"
- },
- {
- "name": "pushpin_black",
- "unicode": "1F588",
- "digest": "80ebac74edb9e8e1f8a219b32a676d318ed73b359cd8193b91b493d775307f63"
+ "digest": "c3f7d7008be6bab8dc02284d4d759abf7aafbb3dbbe3a53f0f5b2ff685af88f8"
},
{
"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_back_of_hand",
+ "unicode": "1F91A",
+ "digest": "20973a697e826625deba5ee3c4f25eb5e1737f2e860ac6fe4ee4d0e0c84b5e12"
+ },
+ {
+ "name": "back_of_hand",
+ "unicode": "1F91A",
+ "digest": "20973a697e826625deba5ee3c4f25eb5e1737f2e860ac6fe4ee4d0e0c84b5e12"
+ },
+ {
+ "name": "raised_back_of_hand_tone1",
+ "unicode": "1F91A-1F3FB",
+ "digest": "06af5941255ca69d10d99d0a512bbda6141a296453835dbccf259ce0afe1dd3d"
+ },
+ {
+ "name": "back_of_hand_tone1",
+ "unicode": "1F91A-1F3FB",
+ "digest": "06af5941255ca69d10d99d0a512bbda6141a296453835dbccf259ce0afe1dd3d"
+ },
+ {
+ "name": "raised_back_of_hand_tone2",
+ "unicode": "1F91A-1F3FC",
+ "digest": "429ed19555c9e5197b729b3e7bd8013346551051cb0b3fbc8a4372717c9a027d"
+ },
+ {
+ "name": "back_of_hand_tone2",
+ "unicode": "1F91A-1F3FC",
+ "digest": "429ed19555c9e5197b729b3e7bd8013346551051cb0b3fbc8a4372717c9a027d"
+ },
+ {
+ "name": "raised_back_of_hand_tone3",
+ "unicode": "1F91A-1F3FD",
+ "digest": "487a1c3f19e77c99b520ec073de2acc4a9e585b739a84b3989f7de85d2c2045c"
+ },
+ {
+ "name": "back_of_hand_tone3",
+ "unicode": "1F91A-1F3FD",
+ "digest": "487a1c3f19e77c99b520ec073de2acc4a9e585b739a84b3989f7de85d2c2045c"
+ },
+ {
+ "name": "raised_back_of_hand_tone4",
+ "unicode": "1F91A-1F3FE",
+ "digest": "154254d8500c55ec3de698be4a352f9bcf06e2950cabc4eabaccad0f39a1e1e9"
+ },
+ {
+ "name": "back_of_hand_tone4",
+ "unicode": "1F91A-1F3FE",
+ "digest": "154254d8500c55ec3de698be4a352f9bcf06e2950cabc4eabaccad0f39a1e1e9"
+ },
+ {
+ "name": "raised_back_of_hand_tone5",
+ "unicode": "1F91A-1F3FF",
+ "digest": "6e9c0855ecd5f14adca5e5862427c3d39ffcf86f7ddd3aaa1fefc3cefc7483c8"
+ },
+ {
+ "name": "back_of_hand_tone5",
+ "unicode": "1F91A-1F3FF",
+ "digest": "6e9c0855ecd5f14adca5e5862427c3d39ffcf86f7ddd3aaa1fefc3cefc7483c8"
},
{
"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,1387 +9217,1472 @@
{
"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": "rhino",
+ "unicode": "1F98F",
+ "digest": "163fa3acd78eead72c431a1f48b8465a6d45272a9169560e456d30b4df93dc6b"
+ },
+ {
+ "name": "rhinoceros",
+ "unicode": "1F98F",
+ "digest": "163fa3acd78eead72c431a1f48b8465a6d45272a9169560e456d30b4df93dc6b"
},
{
"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",
- "unicode": "1F568",
- "digest": "d268bb84be863c0884620dfc6d2a764b0c7466d2f9810549b138e21ac70add4e"
+ "name": "right_facing_fist",
+ "unicode": "1F91C",
+ "digest": "f815d1cc0c0345ddcc8886ae9c133582d7dc779732ac9b93dde1ab4fdd3b251d"
},
{
- "name": "right_speaker_one",
- "unicode": "1F569",
- "digest": "5b92daa87bdf6ee15e798bec382a2ee885f4e6e77a68a3f626adcfe4c782b375"
+ "name": "right_fist",
+ "unicode": "1F91C",
+ "digest": "f815d1cc0c0345ddcc8886ae9c133582d7dc779732ac9b93dde1ab4fdd3b251d"
},
{
- "name": "right_speaker_with_one_sound_wave",
- "unicode": "1F569",
- "digest": "5b92daa87bdf6ee15e798bec382a2ee885f4e6e77a68a3f626adcfe4c782b375"
+ "name": "right_facing_fist_tone1",
+ "unicode": "1F91C-1F3FB",
+ "digest": "0f9269b70cf68071d97389e059a2bdacffd73f2afd2ce6cfd7447bb1a4e9abbb"
},
{
- "name": "right_speaker_three",
- "unicode": "1F56A",
- "digest": "4d00b720a65bd0f4c3682b290b1976ec2388d6ae61225398f4e70556ae9e5f80"
+ "name": "right_fist_tone1",
+ "unicode": "1F91C-1F3FB",
+ "digest": "0f9269b70cf68071d97389e059a2bdacffd73f2afd2ce6cfd7447bb1a4e9abbb"
},
{
- "name": "right_speaker_with_three_sound_waves",
- "unicode": "1F56A",
- "digest": "4d00b720a65bd0f4c3682b290b1976ec2388d6ae61225398f4e70556ae9e5f80"
+ "name": "right_facing_fist_tone2",
+ "unicode": "1F91C-1F3FC",
+ "digest": "32a9833db853972e49e65aa227fb0512c57362da190aa1cc40e1d64f238e837e"
},
{
- "name": "ring",
- "unicode": "1F48D",
- "digest": "ae2a93e7895b9b89f5a39f01d356ffed988f219ef8b658a56c55285826a4533b"
+ "name": "right_fist_tone2",
+ "unicode": "1F91C-1F3FC",
+ "digest": "32a9833db853972e49e65aa227fb0512c57362da190aa1cc40e1d64f238e837e"
+ },
+ {
+ "name": "right_facing_fist_tone3",
+ "unicode": "1F91C-1F3FD",
+ "digest": "be4706f8bb088411f5cbbf9065a0ae5b773c97456bd975c2b6789765657847b9"
+ },
+ {
+ "name": "right_fist_tone3",
+ "unicode": "1F91C-1F3FD",
+ "digest": "be4706f8bb088411f5cbbf9065a0ae5b773c97456bd975c2b6789765657847b9"
+ },
+ {
+ "name": "right_facing_fist_tone4",
+ "unicode": "1F91C-1F3FE",
+ "digest": "1680862891a9d85c4b6f76232a80e2ef7428bcec93087c86eae2efaba9c6a3f7"
+ },
+ {
+ "name": "right_fist_tone4",
+ "unicode": "1F91C-1F3FE",
+ "digest": "1680862891a9d85c4b6f76232a80e2ef7428bcec93087c86eae2efaba9c6a3f7"
},
{
- "name": "ringing_bell",
- "unicode": "1F56D",
- "digest": "d71ab7fa937fc4af507b5b07ea58a4f31e875d9e8304ef2b850d7cebe0e9cd66"
+ "name": "right_facing_fist_tone5",
+ "unicode": "1F91C-1F3FF",
+ "digest": "388715a4bc2178c52bbb3bc2729f57be50acab5d751784c9f3220e86c6b1fbcc"
+ },
+ {
+ "name": "right_fist_tone5",
+ "unicode": "1F91C-1F3FF",
+ "digest": "388715a4bc2178c52bbb3bc2729f57be50acab5d751784c9f3220e86c6b1fbcc"
+ },
+ {
+ "name": "ring",
+ "unicode": "1F48D",
+ "digest": "b5322907222797b5e1786209cda88513e76cd397a40f0a7da24847245c95ef9d"
},
{
"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": "rofl",
+ "unicode": "1F923",
+ "digest": "f4f99ba2ac67b97338f904f9384ff03fb832a2e427bf6e74611bf5fee45f1f48"
+ },
+ {
+ "name": "rolling_on_the_floor_laughing",
+ "unicode": "1F923",
+ "digest": "f4f99ba2ac67b97338f904f9384ff03fb832a2e427bf6e74611bf5fee45f1f48"
},
{
"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"
- },
- {
- "name": "rosette_black",
- "unicode": "1F3F6",
- "digest": "ae8675891c88f9d98463d35178445950c39b0deb0f0e8b3f341228a6e0d0e477"
+ "digest": "2537def4deef422d4e669b28b1a0675259306ab38601019df3ec3482b14e52d5"
},
{
"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": "salad",
+ "unicode": "1F957",
+ "digest": "fe321487ab847abe670e68a83f1d9e096129741c689c769ee7de4a65aeac29f8"
+ },
+ {
+ "name": "green_salad",
+ "unicode": "1F957",
+ "digest": "fe321487ab847abe670e68a83f1d9e096129741c689c769ee7de4a65aeac29f8"
},
{
"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": "scooter",
+ "unicode": "1F6F4",
+ "digest": "4a7db148880398db75e059711cb53edefb6b8fa9d442009f52856b887ab1dde4"
},
{
"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": "second_place",
+ "unicode": "1F948",
+ "digest": "9e2336fc16e532829b55380252f94655b58817d47c909fc2570002c5b06b9c40"
+ },
+ {
+ "name": "second_place_medal",
+ "unicode": "1F948",
+ "digest": "9e2336fc16e532829b55380252f94655b58817d47c909fc2570002c5b06b9c40"
},
{
"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": "selfie",
+ "unicode": "1F933",
+ "digest": "2a1bc9f18ad4d6fb893d91c88ef1b2d9bd063dc2bb1a4b08c248c30f52545d4e"
+ },
+ {
+ "name": "selfie_tone1",
+ "unicode": "1F933-1F3FB",
+ "digest": "26dc212ffed30c276bd6a66a72bc4513e68098a2205fb4ca5b51ccfa1de5b544"
+ },
+ {
+ "name": "selfie_tone2",
+ "unicode": "1F933-1F3FC",
+ "digest": "71eceaefda46e3521f374f76693e7fa8f215067498067900080e2925ca94d7de"
+ },
+ {
+ "name": "selfie_tone3",
+ "unicode": "1F933-1F3FD",
+ "digest": "53eabbd4f6b8ebbd2f7af7bf5cd64309c4039ac1c5b2180290a547deaafcebdf"
+ },
+ {
+ "name": "selfie_tone4",
+ "unicode": "1F933-1F3FE",
+ "digest": "0baad378b09652b99c5d458db2e03b4db14a1557db4ea0969806a0ca1d33d40c"
+ },
+ {
+ "name": "selfie_tone5",
+ "unicode": "1F933-1F3FF",
+ "digest": "9a07608f34ec4dad48764a855f83f3965709d7b2fd2342e6dc9ed61f23f4adfd"
},
{
"name": "seven",
"unicode": "0037-20E3",
- "digest": "c6a34020f6bb25871164fad44302a45c5bffced87f51dfbb816c2985ad7f6a1c"
+ "digest": "ae85172d2c76c44afb4e3b45d277d400abb2dc895244b9abfbd1dac1cd7c53c2"
+ },
+ {
+ "name": "shallow_pan_of_food",
+ "unicode": "1F958",
+ "digest": "7c7ad9d5d3f7226427d310b5853e8257fad899febe58dcbc5adb4677964f5c6d"
+ },
+ {
+ "name": "paella",
+ "unicode": "1F958",
+ "digest": "7c7ad9d5d3f7226427d310b5853e8257fad899febe58dcbc5adb4677964f5c6d"
},
{
"name": "shamrock",
"unicode": "2618",
- "digest": "530e6b987ecb9bcbf0d6e0e11bd075e7949873c784da4f9e1e1b47efd37e5058"
+ "digest": "68ed70c26e04a818439a1742d2da6bc169edd02db86b6e6f8014b651f3235488"
+ },
+ {
+ "name": "shark",
+ "unicode": "1F988",
+ "digest": "23a2364b6356e7bbb84c138e9cf58e2c68cd8caabb337a0c4d365ce87bf5d2da"
},
{
"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": "shopping_cart",
+ "unicode": "1F6D2",
+ "digest": "4599b63f6861cdb4d8272cac84435c24c1d4d6a73c66d51e04a1cd14a1d333e6"
+ },
+ {
+ "name": "shopping_trolley",
+ "unicode": "1F6D2",
+ "digest": "4599b63f6861cdb4d8272cac84435c24c1d4d6a73c66d51e04a1cd14a1d333e6"
},
{
"name": "shower",
"unicode": "1F6BF",
- "digest": "c945120182392510348de9a957c2b77a4645d118691298a2ad660dafa62a859c"
+ "digest": "6b3c767c0eb472d4861c6c3cc2735a5e2c09681872ef42a11dc89f3c80b9da01"
+ },
+ {
+ "name": "shrimp",
+ "unicode": "1F990",
+ "digest": "b3651f3be3767125076a013fe903854f5b456a8afae865cb219cf528e0f44caa"
+ },
+ {
+ "name": "shrug",
+ "unicode": "1F937",
+ "digest": "6e264243cc3b6e396069dea4357a958bdcd4081cb1af0ed6aa47235bef88cf27"
+ },
+ {
+ "name": "shrug_tone1",
+ "unicode": "1F937-1F3FB",
+ "digest": "0567b9fd95c8a857914003a5465a500ca79c8111811d45b865021b1b1d92d0b1"
+ },
+ {
+ "name": "shrug_tone2",
+ "unicode": "1F937-1F3FC",
+ "digest": "1557c2f5e3d4599c806d74c0b78afcca940678787534b6862bb89a20601bac8a"
+ },
+ {
+ "name": "shrug_tone3",
+ "unicode": "1F937-1F3FD",
+ "digest": "f02754541a7bf74ba7eebe6c27daf1e3e1dac25172c35b8ba45641e278dfda3d"
+ },
+ {
+ "name": "shrug_tone4",
+ "unicode": "1F937-1F3FE",
+ "digest": "2b5121164cb5f4e253d8fb31f6445cf8afaf30dba41732edc511440cdb78d15c"
+ },
+ {
+ "name": "shrug_tone5",
+ "unicode": "1F937-1F3FF",
+ "digest": "62d99a26bbad479f574f66208c41b9960cd41fb9d79d3a13fbdaa44682077115"
},
{
"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": "sneezing_face",
+ "unicode": "1F927",
+ "digest": "c20ef571dc7e35572fe3c18b7845aefc89af083ea925c48a29de3b7387af6e17"
+ },
+ {
+ "name": "sneeze",
+ "unicode": "1F927",
+ "digest": "c20ef571dc7e35572fe3c18b7845aefc89af083ea925c48a29de3b7387af6e17"
},
{
"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"
- },
- {
- "name": "speech_left",
- "unicode": "1F5E8",
- "digest": "478b0b07460a9f54b7d0050f886da59fde5e428daa11e899fc31477fda1707ed"
- },
- {
- "name": "left_speech_bubble",
- "unicode": "1F5E8",
- "digest": "478b0b07460a9f54b7d0050f886da59fde5e428daa11e899fc31477fda1707ed"
- },
- {
- "name": "speech_right",
- "unicode": "1F5E9",
- "digest": "8439b13779163c15e678a78b08ebeeb7d131632df21d2a7868de7fed38ca9d8a"
- },
- {
- "name": "right_speech_bubble",
- "unicode": "1F5E9",
- "digest": "8439b13779163c15e678a78b08ebeeb7d131632df21d2a7868de7fed38ca9d8a"
- },
- {
- "name": "speech_three",
- "unicode": "1F5EB",
- "digest": "55a934f3659b6e75fdce0d0c4e2ea56dd34a43892c85a6666bd1882a0bfb92a9"
- },
- {
- "name": "three_speech_bubbles",
- "unicode": "1F5EB",
- "digest": "55a934f3659b6e75fdce0d0c4e2ea56dd34a43892c85a6666bd1882a0bfb92a9"
- },
- {
- "name": "speech_two",
- "unicode": "1F5EA",
- "digest": "0563ef0591da243673cf877462acc5d8e1d980a56e81668ac627de74d0c33983"
- },
- {
- "name": "two_speech_bubbles",
- "unicode": "1F5EA",
- "digest": "0563ef0591da243673cf877462acc5d8e1d980a56e81668ac627de74d0c33983"
+ "digest": "817100d9979456e7d2f253ac22e13b7a2302dc1590566214915b003e403c53ca"
},
{
"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": "spoon",
+ "unicode": "1F944",
+ "digest": "4fa31d59e5bffd2c45a8e01fcd5652e78a5691cbfa744e69882bc67173ddea05"
},
{
"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": "squid",
+ "unicode": "1F991",
+ "digest": "65a1b318c2c506b9d26cfd8282a5cf9922109595c8d12e92c3f7481ac7c08c49"
},
{
"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"
- },
- {
- "name": "stereo",
- "unicode": "1F4FE",
- "digest": "1ce1f9a83867514b8351ad4fd80c46bba04ad67dfb9874e63d7296e1a21161a5"
- },
- {
- "name": "portable_stereo",
- "unicode": "1F4FE",
- "digest": "1ce1f9a83867514b8351ad4fd80c46bba04ad67dfb9874e63d7296e1a21161a5"
+ "digest": "52ad0073f37b978faf3884fb193046f2b0614e1557bbcc9de1b020e42aff2dba"
},
{
"name": "stew",
"unicode": "1F372",
- "digest": "12e6e4bf48a7296700e07a053d831dd67b70c308ca9522ca96e933a4d1ef6c5e"
- },
- {
- "name": "stock_chart",
- "unicode": "1F5E0",
- "digest": "4a0fbf54d19b0b5626f91c932a24e6ac12a65b4fc276d852ff4356c8c579d28a"
+ "digest": "c16f61236db314ad8d9f2dd241ec1e15c8d64e5872cce93ec4d0996490dd39df"
},
{
"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": "stuffed_flatbread",
+ "unicode": "1F959",
+ "digest": "9f841f2520640d69be4f20a3199023d5811842b28556b5e1152e5ec11f0fda07"
+ },
+ {
+ "name": "stuffed_pita",
+ "unicode": "1F959",
+ "digest": "9f841f2520640d69be4f20a3199023d5811842b28556b5e1152e5ec11f0fda07"
},
{
"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"
- },
- {
- "name": "telephone_black",
- "unicode": "1F57F",
- "digest": "c3a42a653a91d90c6b668f678419d5438f2e546050914b841623e57107e805db"
- },
- {
- "name": "black_touchtone_telephone",
- "unicode": "1F57F",
- "digest": "c3a42a653a91d90c6b668f678419d5438f2e546050914b841623e57107e805db"
+ "digest": "3a53851e641f8ad938ce3597b1afca2ea63c9314ff81f62563b99937496a13d7"
},
{
"name": "telephone_receiver",
"unicode": "1F4DE",
- "digest": "e3bf6034de6cf2160893ba4990eba198185a6a3f9cd5767a63b048e41c297640"
- },
- {
- "name": "telephone_white",
- "unicode": "1F57E",
- "digest": "62a7e0e50c53e9f85eba51a92882e6064be05997910d3f7700e1e957dbaf0581"
- },
- {
- "name": "white_touchtone_telephone",
- "unicode": "1F57E",
- "digest": "62a7e0e50c53e9f85eba51a92882e6064be05997910d3f7700e1e957dbaf0581"
+ "digest": "1614d67f3d8814b0d75f39d55f9149e4d28ef57b343498625e62fcfff8365046"
},
{
"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"
- },
- {
- "name": "thought_balloon",
- "unicode": "1F4AD",
- "digest": "76c8513191641f0a79e878ccc0d83c4576984609810633f596db2f64cc684b7d"
- },
- {
- "name": "thought_left",
- "unicode": "1F5EC",
- "digest": "4fd591bf4318df73d1b17f434a449d8e95f49cca53a3d8f4d1ca983f3809ef46"
+ "digest": "4f0b84e5ab8a650cafb166e93688f0e9b31b9ade22a91035261ac90490edb9d3"
},
{
- "name": "left_thought_bubble",
- "unicode": "1F5EC",
- "digest": "4fd591bf4318df73d1b17f434a449d8e95f49cca53a3d8f4d1ca983f3809ef46"
+ "name": "third_place",
+ "unicode": "1F949",
+ "digest": "27c9bcba44ad95bee30882cc0722e8b0a798206306655dd648e884447ed26808"
},
{
- "name": "thought_right",
- "unicode": "1F5ED",
- "digest": "0e8c0ce26e2d0e30894f5394b0736456e8268f775e0e7eda4c7dc3c2ff9231ae"
+ "name": "third_place_medal",
+ "unicode": "1F949",
+ "digest": "27c9bcba44ad95bee30882cc0722e8b0a798206306655dd648e884447ed26808"
},
{
- "name": "right_thought_bubble",
- "unicode": "1F5ED",
- "digest": "0e8c0ce26e2d0e30894f5394b0736456e8268f775e0e7eda4c7dc3c2ff9231ae"
+ "name": "thought_balloon",
+ "unicode": "1F4AD",
+ "digest": "bf59624560c333561d636aedf2c8827089e275895cf434974daaabb3d5cea46e"
},
{
"name": "three",
"unicode": "0033-20E3",
- "digest": "ca0147a8f67cea3bc2516fa8deef4325188359559786c94ff0b27f90eef04b88"
- },
- {
- "name": "thumbs_down_reverse",
- "unicode": "1F593",
- "digest": "a8b561e389bc4e4b07fba70994f6445e5ddc6afe68922fcb6e9e7282d19ad958"
- },
- {
- "name": "reversed_thumbs_down_sign",
- "unicode": "1F593",
- "digest": "a8b561e389bc4e4b07fba70994f6445e5ddc6afe68922fcb6e9e7282d19ad958"
- },
- {
- "name": "thumbs_up_reverse",
- "unicode": "1F592",
- "digest": "b6e52715c5ce590bfd08f6e05058ec3765ea2da341b11f9825d100608b173837"
- },
- {
- "name": "reversed_thumbs_up_sign",
- "unicode": "1F592",
- "digest": "b6e52715c5ce590bfd08f6e05058ec3765ea2da341b11f9825d100608b173837"
+ "digest": "d3f85828787799c769655c38a519cad0743ab799ab276c7606e6e6894cc442e6"
},
{
"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,607 +10712,617 @@
{
"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"
- },
- {
- "name": "train_diesel",
- "unicode": "1F6F2",
- "digest": "621bb967cd93fa9f8fd4b155965cc7572d3f91f88d94938ba10c8626718b623c"
- },
- {
- "name": "diesel_locomotive",
- "unicode": "1F6F2",
- "digest": "621bb967cd93fa9f8fd4b155965cc7572d3f91f88d94938ba10c8626718b623c"
+ "digest": "06e65d549e771632f3c64287a38ba67236f9800ccb6a23c3b592bc010e24e122"
},
{
"name": "tram",
"unicode": "1F68A",
- "digest": "5a86d31f7ab677d967fecd75babc900b5169766d0228961912314c4c4d1d64ee"
- },
- {
- "name": "triangle_round",
- "unicode": "1F6C6",
- "digest": "e24bb39ecfaaa746b03dc8418697d09ef327d5b077db39014f39d5fb87e23bd5"
- },
- {
- "name": "triangle_with_rounded_corners",
- "unicode": "1F6C6",
- "digest": "e24bb39ecfaaa746b03dc8418697d09ef327d5b077db39014f39d5fb87e23bd5"
+ "digest": "21a7699f1a94f06dcb4d1e896448b98a4205f8efe902a8ac169a5005d11ab100"
},
{
"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"
+ "name": "tumbler_glass",
+ "unicode": "1F943",
+ "digest": "7a38658274b9ff28836725a1dbfad49b8fa3af5ec8385e629db6bfdc7d93907a"
},
{
- "name": "turned_ok_hand",
- "unicode": "1F58F",
- "digest": "8a6c5b7d4c737866e7e32c6d9f7f447a48a0ac57a8909d43f87367d4a9b59246"
+ "name": "whisky",
+ "unicode": "1F943",
+ "digest": "7a38658274b9ff28836725a1dbfad49b8fa3af5ec8385e629db6bfdc7d93907a"
},
{
- "name": "turned_ok_hand_sign",
- "unicode": "1F58F",
- "digest": "8a6c5b7d4c737866e7e32c6d9f7f447a48a0ac57a8909d43f87367d4a9b59246"
+ "name": "turkey",
+ "unicode": "1F983",
+ "digest": "bf5daef15716b66636a5fdb6d059420521443c0603e2d56bd7c99c791a7285f4"
},
{
"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": "water_polo",
+ "unicode": "1F93D",
+ "digest": "fc77e1d2a84a9f4cf0cf19c1ea10cf137cf0940b9103a523121eda87677ad148"
+ },
+ {
+ "name": "water_polo_tone1",
+ "unicode": "1F93D-1F3FB",
+ "digest": "3be28384edd29ada8109f07720d601a9d5866ed63e6234efe9ee1a194ed5d0c5"
+ },
+ {
+ "name": "water_polo_tone2",
+ "unicode": "1F93D-1F3FC",
+ "digest": "afcd3f28c6719f869ca79a6fd1ccade2ea976ade844fbc1081fc72865bcb652f"
+ },
+ {
+ "name": "water_polo_tone3",
+ "unicode": "1F93D-1F3FD",
+ "digest": "d19481c9b82d9413e99c2652e020fd763f2b54408dedaffec8dfe80973ded407"
+ },
+ {
+ "name": "water_polo_tone4",
+ "unicode": "1F93D-1F3FE",
+ "digest": "375972d882b627e8d525e632e58b30346fc3e01858d7d08d62a9d3bf8132bbc7"
+ },
+ {
+ "name": "water_polo_tone5",
+ "unicode": "1F93D-1F3FF",
+ "digest": "a8e1ced1c5382a8147a1d1801a133cada9a0e52e41de6272e56c3c1f426f6048"
},
{
"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,226 +11332,291 @@
{
"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": "wilted_rose",
+ "unicode": "1F940",
+ "digest": "2c9e01ab9a61d057c71478b09ba7d82ae08f4a5a1c2212b7ad562b74f616677f"
+ },
+ {
+ "name": "wilted_flower",
+ "unicode": "1F940",
+ "digest": "2c9e01ab9a61d057c71478b09ba7d82ae08f4a5a1c2212b7ad562b74f616677f"
},
{
"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",
- "unicode": "1F58E",
- "digest": "c4fc18ece6778339ebe14438aaf570e22385c3010c2d341824fa72ac6068cfeb"
+ "name": "wrestlers",
+ "unicode": "1F93C",
+ "digest": "9be983f3f9438f3ab8f6b643a958371d1e710c6d78e728f3465141811f05c2d5"
+ },
+ {
+ "name": "wrestling",
+ "unicode": "1F93C",
+ "digest": "9be983f3f9438f3ab8f6b643a958371d1e710c6d78e728f3465141811f05c2d5"
},
{
- "name": "left_writing_hand",
- "unicode": "1F58E",
- "digest": "c4fc18ece6778339ebe14438aaf570e22385c3010c2d341824fa72ac6068cfeb"
+ "name": "wrestlers_tone1",
+ "unicode": "1F93C-1F3FB",
+ "digest": "60461f83bfc93ce59dd027eab4782b7f206a7b142719fa72f301e047dc83a5d9"
+ },
+ {
+ "name": "wrestling_tone1",
+ "unicode": "1F93C-1F3FB",
+ "digest": "60461f83bfc93ce59dd027eab4782b7f206a7b142719fa72f301e047dc83a5d9"
+ },
+ {
+ "name": "wrestlers_tone2",
+ "unicode": "1F93C-1F3FC",
+ "digest": "67ad93c86e6c58d552c18e7a0105cc81fd9bb0474da51f788eba2e4c14b4a636"
+ },
+ {
+ "name": "wrestling_tone2",
+ "unicode": "1F93C-1F3FC",
+ "digest": "67ad93c86e6c58d552c18e7a0105cc81fd9bb0474da51f788eba2e4c14b4a636"
+ },
+ {
+ "name": "wrestlers_tone3",
+ "unicode": "1F93C-1F3FD",
+ "digest": "6bfd06c4435cabf2def153912040e05bf8db424fa383148ddda6d0ce8a8a3349"
+ },
+ {
+ "name": "wrestling_tone3",
+ "unicode": "1F93C-1F3FD",
+ "digest": "6bfd06c4435cabf2def153912040e05bf8db424fa383148ddda6d0ce8a8a3349"
+ },
+ {
+ "name": "wrestlers_tone4",
+ "unicode": "1F93C-1F3FE",
+ "digest": "597312678834c4d288c238482879856d5eba4620deb1eaef495f428e2ba5f2a5"
+ },
+ {
+ "name": "wrestling_tone4",
+ "unicode": "1F93C-1F3FE",
+ "digest": "597312678834c4d288c238482879856d5eba4620deb1eaef495f428e2ba5f2a5"
+ },
+ {
+ "name": "wrestlers_tone5",
+ "unicode": "1F93C-1F3FF",
+ "digest": "d6aebdf1e44fd825b9a5b3716aefbc53f4b4dbb73cb2a628c0f2994ebfd34614"
+ },
+ {
+ "name": "wrestling_tone5",
+ "unicode": "1F93C-1F3FF",
+ "digest": "d6aebdf1e44fd825b9a5b3716aefbc53f4b4dbb73cb2a628c0f2994ebfd34614"
+ },
+ {
+ "name": "writing_hand",
+ "unicode": "270D",
+ "digest": "110517ae4da5587e8b0662881658e27da4120bfacec54734fd6657831d4d782f"
},
{
"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/fixtures/emojis/index.json b/fixtures/emojis/index.json
index 7f204c1a8e0..2a990913b9c 100644
--- a/fixtures/emojis/index.json
+++ b/fixtures/emojis/index.json
@@ -4,7 +4,7 @@
"unicode_alternates": [],
"name": "hundred points symbol",
"shortname": ":100:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -15,12 +15,14 @@
"percent",
"a",
"plus",
- "perfect",
"school",
"quiz",
- "score",
"test",
- "exam"
+ "exam",
+ "symbol",
+ "wow",
+ "win",
+ "parties"
],
"moji": "💯"
},
@@ -29,12 +31,13 @@
"unicode_alternates": [],
"name": "input symbol for numbers",
"shortname": ":1234:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"blue-square",
- "numbers"
+ "numbers",
+ "symbol"
],
"moji": "🔢"
},
@@ -43,16 +46,20 @@
"unicode_alternates": [],
"name": "billiards",
"shortname": ":8ball:",
- "category": "objects",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"pool",
"billiards",
"eight ball",
- "pool",
"pocket ball",
- "cue"
+ "cue",
+ "game",
+ "ball",
+ "sport",
+ "luck",
+ "boys night"
],
"moji": "🎱"
},
@@ -61,13 +68,14 @@
"unicode_alternates": [],
"name": "negative squared latin capital letter a",
"shortname": ":a:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"alphabet",
"letter",
- "red-square"
+ "red-square",
+ "symbol"
],
"moji": "🅰"
},
@@ -76,12 +84,13 @@
"unicode_alternates": [],
"name": "negative squared ab",
"shortname": ":ab:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"alphabet",
- "red-square"
+ "red-square",
+ "symbol"
],
"moji": "🆎"
},
@@ -90,12 +99,13 @@
"unicode_alternates": [],
"name": "input symbol for latin letters",
"shortname": ":abc:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"alphabet",
- "blue-square"
+ "blue-square",
+ "symbol"
],
"moji": "🔤"
},
@@ -104,12 +114,13 @@
"unicode_alternates": [],
"name": "input symbol for latin small letters",
"shortname": ":abcd:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"alphabet",
- "blue-square"
+ "blue-square",
+ "symbol"
],
"moji": "🔡"
},
@@ -118,7 +129,7 @@
"unicode_alternates": [],
"name": "circled ideograph accept",
"shortname": ":accept:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -127,7 +138,8 @@
"good",
"kanji",
"ok",
- "yes"
+ "yes",
+ "symbol"
],
"moji": "🉑"
},
@@ -136,7 +148,7 @@
"unicode_alternates": [],
"name": "aerial tramway",
"shortname": ":aerial_tramway:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -146,7 +158,9 @@
"tram",
"tramway",
"cable",
- "transport"
+ "transport",
+ "travel",
+ "train"
],
"moji": "🚡"
},
@@ -157,7 +171,7 @@
],
"name": "airplane",
"shortname": ":airplane:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -173,7 +187,8 @@
"jet",
"jumbo",
"boeing",
- "airbus"
+ "airbus",
+ "vacation"
],
"moji": "✈"
},
@@ -182,7 +197,7 @@
"unicode_alternates": [],
"name": "airplane arriving",
"shortname": ":airplane_arriving:",
- "category": "travel_places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -197,15 +212,17 @@
"jet",
"jumbo",
"boeing",
- "airbus"
- ]
+ "airbus",
+ "vacation"
+ ],
+ "moji": "🛬"
},
"airplane_departure": {
"unicode": "1F6EB",
"unicode_alternates": [],
"name": "airplane departure",
"shortname": ":airplane_departure:",
- "category": "travel_places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -221,30 +238,17 @@
"jumbo",
"boeing",
"airbus",
- "leaving"
- ]
- },
- "airplane_northeast": {
- "unicode": "1F6EA",
- "unicode_alternates": [],
- "name": "northeast-pointing airplane",
- "shortname": ":airplane_northeast:",
- "category": "travel_places",
- "aliases": [
- ":northeast_pointing_airplane:"
+ "leaving",
+ "vacation"
],
- "aliases_ascii": [],
- "keywords": [
- "plane",
- "travel"
- ]
+ "moji": "🛫"
},
"airplane_small": {
"unicode": "1F6E9",
"unicode_alternates": [],
"name": "small airplane",
"shortname": ":airplane_small:",
- "category": "travel_places",
+ "category": "travel",
"aliases": [
":small_airplane:"
],
@@ -261,38 +265,10 @@
"jet",
"jumbo",
"boeing",
- "airbus"
- ]
- },
- "airplane_small_up": {
- "unicode": "1F6E8",
- "unicode_alternates": [],
- "name": "up-pointing small airplane",
- "shortname": ":airplane_small_up:",
- "category": "travel_places",
- "aliases": [
- ":up_pointing_small_airplane:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "plane",
- "travel"
- ]
- },
- "airplane_up": {
- "unicode": "1F6E7",
- "unicode_alternates": [],
- "name": "up-pointing airplane",
- "shortname": ":airplane_up:",
- "category": "travel_places",
- "aliases": [
- ":up_pointing_airplane:"
+ "airbus",
+ "vacation"
],
- "aliases_ascii": [],
- "keywords": [
- "plane",
- "travel"
- ]
+ "moji": "🛩"
},
"alarm_clock": {
"unicode": "23F0",
@@ -304,13 +280,14 @@
"aliases_ascii": [],
"keywords": [
"time",
- "wake"
+ "wake",
+ "object"
],
"moji": "⏰"
},
"alembic": {
"unicode": "2697",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "alembic",
"shortname": ":alembic:",
"category": "objects",
@@ -319,22 +296,27 @@
"keywords": [
"chemistry",
"object",
- "tool"
- ]
+ "tool",
+ "science"
+ ],
+ "moji": "⚗"
},
"alien": {
"unicode": "1F47D",
"unicode_alternates": [],
"name": "extraterrestrial alien",
"shortname": ":alien:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"UFO",
"paul",
"alien",
- "ufo"
+ "ufo",
+ "space",
+ "monster",
+ "scientology"
],
"moji": "👽"
},
@@ -343,7 +325,7 @@
"unicode_alternates": [],
"name": "ambulance",
"shortname": ":ambulance:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -353,19 +335,23 @@
"emergency",
"medical",
"help",
- "assistance"
+ "assistance",
+ "transportation"
],
"moji": "🚑"
},
"amphora": {
"unicode": "1F3FA",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "amphora",
"shortname": ":amphora:",
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "object"
+ ],
+ "moji": "🏺"
},
"anchor": {
"unicode": "2693",
@@ -374,21 +360,23 @@
],
"name": "anchor",
"shortname": ":anchor:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"ferry",
"ship",
"anchor",
- "ship",
"boat",
"ocean",
"harbor",
"marina",
"shipyard",
"sailor",
- "tattoo"
+ "tattoo",
+ "object",
+ "travel",
+ "vacation"
],
"moji": "⚓"
},
@@ -397,7 +385,7 @@
"unicode_alternates": [],
"name": "baby angel",
"shortname": ":angel:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -406,16 +394,17 @@
"halo",
"cupid",
"wings",
- "halo",
"heaven",
- "wings",
- "jesus"
+ "jesus",
+ "people",
+ "diversity",
+ "omg"
],
"moji": "👼"
},
"angel_tone1": {
"unicode": "1F47C-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "baby angel tone 1",
"shortname": ":angel_tone1:",
"category": "people",
@@ -427,11 +416,12 @@
"heaven",
"wings",
"jesus"
- ]
+ ],
+ "moji": "👼🏻"
},
"angel_tone2": {
"unicode": "1F47C-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "baby angel tone 2",
"shortname": ":angel_tone2:",
"category": "people",
@@ -443,11 +433,12 @@
"heaven",
"wings",
"jesus"
- ]
+ ],
+ "moji": "👼🏼"
},
"angel_tone3": {
"unicode": "1F47C-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "baby angel tone 3",
"shortname": ":angel_tone3:",
"category": "people",
@@ -459,11 +450,12 @@
"heaven",
"wings",
"jesus"
- ]
+ ],
+ "moji": "👼🏽"
},
"angel_tone4": {
"unicode": "1F47C-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "baby angel tone 4",
"shortname": ":angel_tone4:",
"category": "people",
@@ -475,11 +467,12 @@
"heaven",
"wings",
"jesus"
- ]
+ ],
+ "moji": "👼🏾"
},
"angel_tone5": {
"unicode": "1F47C-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "baby angel tone 5",
"shortname": ":angel_tone5:",
"category": "people",
@@ -491,50 +484,31 @@
"heaven",
"wings",
"jesus"
- ]
+ ],
+ "moji": "👼🏿"
},
"anger": {
"unicode": "1F4A2",
"unicode_alternates": [],
"name": "anger symbol",
"shortname": ":anger:",
- "category": "emoticons",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"anger",
"angry",
- "mad"
+ "mad",
+ "symbol"
],
"moji": "💢"
},
- "anger_left": {
- "unicode": "1F5EE",
- "unicode_alternates": [],
- "name": "left anger bubble",
- "shortname": ":anger_left:",
- "category": "objects_symbols",
- "aliases": [
- ":left_anger_bubble:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "speech",
- "balloon",
- "talk",
- "mood",
- "conversation",
- "communication",
- "comic",
- "angry"
- ]
- },
"anger_right": {
"unicode": "1F5EF",
"unicode_alternates": [],
"name": "right anger bubble",
"shortname": ":anger_right:",
- "category": "objects_symbols",
+ "category": "symbols",
"aliases": [
":right_anger_bubble:"
],
@@ -547,15 +521,17 @@
"conversation",
"communication",
"comic",
- "angry"
- ]
+ "angry",
+ "symbol"
+ ],
+ "moji": "🗯"
},
"angry": {
"unicode": "1F620",
"unicode_alternates": [],
"name": "angry face",
"shortname": ":angry:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [
">:(",
@@ -571,7 +547,8 @@
"annoyed",
"face",
"frustrated",
- "mad"
+ "smiley",
+ "emotion"
],
"moji": "😠"
},
@@ -580,7 +557,7 @@
"unicode_alternates": [],
"name": "anguished face",
"shortname": ":anguished:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -592,7 +569,11 @@
"ouch",
"misery",
"distress",
- "grief"
+ "grief",
+ "sad",
+ "smiley",
+ "surprised",
+ "emotion"
],
"moji": "😧"
},
@@ -609,8 +590,8 @@
"insect",
"ant",
"queen",
- "insect",
- "team"
+ "team",
+ "insects"
],
"moji": "🐜"
},
@@ -619,20 +600,21 @@
"unicode_alternates": [],
"name": "red apple",
"shortname": ":apple:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"fruit",
"mac",
"apple",
- "fruit",
"electronics",
"red",
"doctor",
"teacher",
"school",
- "core"
+ "core",
+ "food",
+ "creationism"
],
"moji": "🍎"
},
@@ -643,7 +625,7 @@
],
"name": "aquarius",
"shortname": ":aquarius:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -657,9 +639,8 @@
"zodiac",
"sign",
"purple-square",
- "sign",
- "zodiac",
- "horoscope"
+ "horoscope",
+ "symbol"
],
"moji": "♒"
},
@@ -670,7 +651,7 @@
],
"name": "aries",
"shortname": ":aries:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -683,9 +664,8 @@
"zodiac",
"sign",
"purple-square",
- "sign",
- "zodiac",
- "horoscope"
+ "horoscope",
+ "symbol"
],
"moji": "♈"
},
@@ -696,12 +676,14 @@
],
"name": "black left-pointing triangle",
"shortname": ":arrow_backward:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"arrow",
- "blue-square"
+ "blue-square",
+ "symbol",
+ "triangle"
],
"moji": "◀"
},
@@ -710,12 +692,13 @@
"unicode_alternates": [],
"name": "black down-pointing double triangle",
"shortname": ":arrow_double_down:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"arrow",
- "blue-square"
+ "blue-square",
+ "symbol"
],
"moji": "⏬"
},
@@ -724,12 +707,13 @@
"unicode_alternates": [],
"name": "black up-pointing double triangle",
"shortname": ":arrow_double_up:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"arrow",
- "blue-square"
+ "blue-square",
+ "symbol"
],
"moji": "⏫"
},
@@ -740,12 +724,13 @@
],
"name": "downwards black arrow",
"shortname": ":arrow_down:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"arrow",
- "blue-square"
+ "blue-square",
+ "symbol"
],
"moji": "⬇"
},
@@ -754,12 +739,14 @@
"unicode_alternates": [],
"name": "down-pointing small red triangle",
"shortname": ":arrow_down_small:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"arrow",
- "blue-square"
+ "blue-square",
+ "symbol",
+ "triangle"
],
"moji": "🔽"
},
@@ -770,12 +757,14 @@
],
"name": "black right-pointing triangle",
"shortname": ":arrow_forward:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"arrow",
- "blue-square"
+ "blue-square",
+ "symbol",
+ "triangle"
],
"moji": "▶"
},
@@ -786,12 +775,13 @@
],
"name": "arrow pointing rightwards then curving downwards",
"shortname": ":arrow_heading_down:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"arrow",
- "blue-square"
+ "blue-square",
+ "symbol"
],
"moji": "⤵"
},
@@ -802,12 +792,13 @@
],
"name": "arrow pointing rightwards then curving upwards",
"shortname": ":arrow_heading_up:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"arrow",
- "blue-square"
+ "blue-square",
+ "symbol"
],
"moji": "⤴"
},
@@ -818,13 +809,14 @@
],
"name": "leftwards black arrow",
"shortname": ":arrow_left:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"arrow",
"blue-square",
- "previous"
+ "previous",
+ "symbol"
],
"moji": "⬅"
},
@@ -835,12 +827,13 @@
],
"name": "south west arrow",
"shortname": ":arrow_lower_left:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"arrow",
- "blue-square"
+ "blue-square",
+ "symbol"
],
"moji": "↙"
},
@@ -851,12 +844,13 @@
],
"name": "south east arrow",
"shortname": ":arrow_lower_right:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"arrow",
- "blue-square"
+ "blue-square",
+ "symbol"
],
"moji": "↘"
},
@@ -867,12 +861,14 @@
],
"name": "black rightwards arrow",
"shortname": ":arrow_right:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"blue-square",
- "next"
+ "next",
+ "arrow",
+ "symbol"
],
"moji": "➡"
},
@@ -883,11 +879,13 @@
],
"name": "rightwards arrow with hook",
"shortname": ":arrow_right_hook:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "blue-square"
+ "blue-square",
+ "arrow",
+ "symbol"
],
"moji": "↪"
},
@@ -898,11 +896,13 @@
],
"name": "upwards black arrow",
"shortname": ":arrow_up:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "blue-square"
+ "blue-square",
+ "arrow",
+ "symbol"
],
"moji": "⬆"
},
@@ -913,11 +913,13 @@
],
"name": "up down arrow",
"shortname": ":arrow_up_down:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "blue-square"
+ "blue-square",
+ "arrow",
+ "symbol"
],
"moji": "↕"
},
@@ -926,11 +928,14 @@
"unicode_alternates": [],
"name": "up-pointing small red triangle",
"shortname": ":arrow_up_small:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "blue-square"
+ "blue-square",
+ "arrow",
+ "symbol",
+ "triangle"
],
"moji": "🔼"
},
@@ -941,11 +946,13 @@
],
"name": "north west arrow",
"shortname": ":arrow_upper_left:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "blue-square"
+ "blue-square",
+ "arrow",
+ "symbol"
],
"moji": "↖"
},
@@ -956,11 +963,13 @@
],
"name": "north east arrow",
"shortname": ":arrow_upper_right:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "blue-square"
+ "blue-square",
+ "arrow",
+ "symbol"
],
"moji": "↗"
},
@@ -969,11 +978,13 @@
"unicode_alternates": [],
"name": "clockwise downwards and upwards open circle arrows",
"shortname": ":arrows_clockwise:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "sync"
+ "sync",
+ "arrow",
+ "symbol"
],
"moji": "🔃"
},
@@ -982,12 +993,14 @@
"unicode_alternates": [],
"name": "anticlockwise downwards and upwards open circle ar",
"shortname": ":arrows_counterclockwise:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"blue-square",
- "sync"
+ "sync",
+ "arrow",
+ "symbol"
],
"moji": "🔄"
},
@@ -996,7 +1009,7 @@
"unicode_alternates": [],
"name": "artist palette",
"shortname": ":art:",
- "category": "objects",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -1007,8 +1020,6 @@
"palette",
"art",
"colors",
- "paint",
- "draw",
"brush",
"pastels",
"oils"
@@ -1020,7 +1031,7 @@
"unicode_alternates": [],
"name": "articulated lorry",
"shortname": ":articulated_lorry:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -1035,24 +1046,11 @@
],
"moji": "🚛"
},
- "ascending_notes": {
- "unicode": "1F39C",
- "unicode_alternates": [],
- "name": "beamed ascending musical notes",
- "shortname": ":ascending_notes:",
- "category": "objects_symbols",
- "aliases": [],
- "aliases_ascii": [],
- "keywords": [
- "score",
- "music",
- "sound",
- "tone"
- ]
- },
"asterisk": {
"unicode": "002A-20E3",
- "unicode_alternates": "002a-fe0f-20e3",
+ "unicode_alternates": [
+ "002A-FE0F-20E3"
+ ],
"name": "keycap asterisk",
"shortname": ":asterisk:",
"category": "symbols",
@@ -1064,14 +1062,15 @@
"*",
"star",
"symbol"
- ]
+ ],
+ "moji": "*⃣"
},
"astonished": {
"unicode": "1F632",
"unicode_alternates": [],
"name": "astonished face",
"shortname": ":astonished:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -1079,7 +1078,12 @@
"xox",
"shocked",
"surprise",
- "astonished"
+ "astonished",
+ "smiley",
+ "surprised",
+ "wow",
+ "emotion",
+ "omg"
],
"moji": "😲"
},
@@ -1088,12 +1092,16 @@
"unicode_alternates": [],
"name": "athletic shoe",
"shortname": ":athletic_shoe:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"shoes",
- "sports"
+ "sports",
+ "fashion",
+ "shoe",
+ "accessories",
+ "boys night"
],
"moji": "👟"
},
@@ -1102,7 +1110,7 @@
"unicode_alternates": [],
"name": "automated teller machine",
"shortname": ":atm:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -1115,17 +1123,16 @@
"bank",
"adam",
"payday",
- "bank",
"blue-square",
- "cash",
- "money",
- "payment"
+ "payment",
+ "electronics",
+ "symbol"
],
"moji": "🏧"
},
"atom": {
"unicode": "269B",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "atom symbol",
"shortname": ":atom:",
"category": "symbols",
@@ -1134,21 +1141,36 @@
],
"aliases_ascii": [],
"keywords": [
- "atheist"
- ]
+ "atheist",
+ "symbol",
+ "science"
+ ],
+ "moji": "⚛"
+ },
+ "avocado": {
+ "unicode": "1F951",
+ "unicode_alternates": [],
+ "name": "avocado",
+ "shortname": ":avocado:",
+ "category": "food",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🥑"
},
"b": {
"unicode": "1F171",
"unicode_alternates": [],
"name": "negative squared latin capital letter b",
"shortname": ":b:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"alphabet",
"letter",
- "red-square"
+ "red-square",
+ "symbol"
],
"moji": "🅱"
},
@@ -1157,13 +1179,16 @@
"unicode_alternates": [],
"name": "baby",
"shortname": ":baby:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"boy",
"child",
- "infant"
+ "infant",
+ "people",
+ "baby",
+ "diversity"
],
"moji": "👶"
},
@@ -1172,7 +1197,7 @@
"unicode_alternates": [],
"name": "baby bottle",
"shortname": ":baby_bottle:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -1184,7 +1209,9 @@
"mother",
"nipple",
"newborn",
- "formula"
+ "formula",
+ "drink",
+ "object"
],
"moji": "🍼"
},
@@ -1202,7 +1229,6 @@
"chick",
"baby",
"bird",
- "chicken",
"young",
"woman",
"cute"
@@ -1214,7 +1240,7 @@
"unicode_alternates": [],
"name": "baby symbol",
"shortname": ":baby_symbol:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -1226,13 +1252,14 @@
"human",
"diaper",
"small",
- "babe"
+ "babe",
+ "symbol"
],
"moji": "🚼"
},
"baby_tone1": {
"unicode": "1F476-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "baby tone 1",
"shortname": ":baby_tone1:",
"category": "people",
@@ -1242,11 +1269,12 @@
"child",
"infant",
"toddler"
- ]
+ ],
+ "moji": "👶🏻"
},
"baby_tone2": {
"unicode": "1F476-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "baby tone 2",
"shortname": ":baby_tone2:",
"category": "people",
@@ -1256,11 +1284,12 @@
"child",
"infant",
"toddler"
- ]
+ ],
+ "moji": "👶🏼"
},
"baby_tone3": {
"unicode": "1F476-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "baby tone 3",
"shortname": ":baby_tone3:",
"category": "people",
@@ -1270,11 +1299,12 @@
"child",
"infant",
"toddler"
- ]
+ ],
+ "moji": "👶🏽"
},
"baby_tone4": {
"unicode": "1F476-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "baby tone 4",
"shortname": ":baby_tone4:",
"category": "people",
@@ -1284,11 +1314,12 @@
"child",
"infant",
"toddler"
- ]
+ ],
+ "moji": "👶🏾"
},
"baby_tone5": {
"unicode": "1F476-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "baby tone 5",
"shortname": ":baby_tone5:",
"category": "people",
@@ -1298,37 +1329,57 @@
"child",
"infant",
"toddler"
- ]
+ ],
+ "moji": "👶🏿"
},
"back": {
"unicode": "1F519",
"unicode_alternates": [],
"name": "back with leftwards arrow above",
"shortname": ":back:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "arrow"
+ "arrow",
+ "symbol"
],
"moji": "🔙"
},
+ "bacon": {
+ "unicode": "1F953",
+ "unicode_alternates": [],
+ "name": "bacon",
+ "shortname": ":bacon:",
+ "category": "food",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "pig"
+ ],
+ "moji": "🥓"
+ },
"badminton": {
"unicode": "1F3F8",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "badminton racquet",
"shortname": ":badminton:",
"category": "activity",
"aliases": [],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "game",
+ "sport",
+ "badminton"
+ ],
+ "moji": "🏸"
},
"baggage_claim": {
"unicode": "1F6C4",
"unicode_alternates": [],
"name": "baggage claim",
"shortname": ":baggage_claim:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -1338,7 +1389,8 @@
"bag",
"baggage",
"luggage",
- "travel"
+ "travel",
+ "symbol"
],
"moji": "🛄"
},
@@ -1355,11 +1407,13 @@
"party",
"balloon",
"birthday",
- "celebration",
"helium",
"gas",
"children",
- "float"
+ "float",
+ "object",
+ "good",
+ "parties"
],
"moji": "🎈"
},
@@ -1368,29 +1422,17 @@
"unicode_alternates": [],
"name": "ballot box with ballot",
"shortname": ":ballot_box:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [
":ballot_box_with_ballot:"
],
"aliases_ascii": [],
"keywords": [
- "vote"
- ]
- },
- "ballot_box_check": {
- "unicode": "1F5F9",
- "unicode_alternates": [],
- "name": "ballot box with bold check",
- "shortname": ":ballot_box_check:",
- "category": "objects_symbols",
- "aliases": [
- ":ballot_box_with_bold_check:"
+ "vote",
+ "object",
+ "office"
],
- "aliases_ascii": [],
- "keywords": [
- "mark",
- "vote"
- ]
+ "moji": "🗳"
},
"ballot_box_with_check": {
"unicode": "2611",
@@ -1399,51 +1441,22 @@
],
"name": "ballot box with check",
"shortname": ":ballot_box_with_check:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"agree",
- "ok"
+ "ok",
+ "symbol"
],
"moji": "☑"
},
- "ballot_box_x": {
- "unicode": "1F5F5",
- "unicode_alternates": [],
- "name": "ballot box with script x",
- "shortname": ":ballot_box_x:",
- "category": "objects_symbols",
- "aliases": [
- ":ballot_box_with_script_x:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "mark",
- "vote"
- ]
- },
- "ballot_x": {
- "unicode": "1F5F4",
- "unicode_alternates": [],
- "name": "ballot script x",
- "shortname": ":ballot_x:",
- "category": "objects_symbols",
- "aliases": [
- ":ballot_script_x:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "mark",
- "vote"
- ]
- },
"bamboo": {
"unicode": "1F38D",
"unicode_alternates": [],
"name": "pine decoration",
"shortname": ":bamboo:",
- "category": "objects",
+ "category": "nature",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -1472,7 +1485,7 @@
"unicode_alternates": [],
"name": "banana",
"shortname": ":banana:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -1480,7 +1493,8 @@
"fruit",
"banana",
"peel",
- "bunch"
+ "bunch",
+ "penis"
],
"moji": "🍌"
},
@@ -1491,12 +1505,14 @@
],
"name": "double exclamation mark",
"shortname": ":bangbang:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"exclamation",
- "surprise"
+ "surprise",
+ "symbol",
+ "punctuation"
],
"moji": "‼"
},
@@ -1505,11 +1521,12 @@
"unicode_alternates": [],
"name": "bank",
"shortname": ":bank:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "building"
+ "building",
+ "places"
],
"moji": "🏦"
},
@@ -1524,7 +1541,9 @@
"keywords": [
"graph",
"presentation",
- "stats"
+ "stats",
+ "work",
+ "office"
],
"moji": "📊"
},
@@ -1533,13 +1552,14 @@
"unicode_alternates": [],
"name": "barber pole",
"shortname": ":barber:",
- "category": "places",
+ "category": "objects",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"hair",
"salon",
- "style"
+ "style",
+ "object"
],
"moji": "💈"
},
@@ -1550,13 +1570,17 @@
],
"name": "baseball",
"shortname": ":baseball:",
- "category": "objects",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"MLB",
"balls",
- "sports"
+ "sports",
+ "game",
+ "ball",
+ "sport",
+ "baseball"
],
"moji": "⚾"
},
@@ -1565,7 +1589,7 @@
"unicode_alternates": [],
"name": "basketball and hoop",
"shortname": ":basketball:",
- "category": "objects",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -1578,13 +1602,16 @@
"hoop",
"net",
"swish",
- "rip city"
+ "rip city",
+ "game",
+ "ball",
+ "sport"
],
"moji": "🏀"
},
"basketball_player": {
"unicode": "26F9",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person with ball",
"shortname": ":basketball_player:",
"category": "activity",
@@ -1594,12 +1621,18 @@
"aliases_ascii": [],
"keywords": [
"sport",
- "travel"
- ]
+ "travel",
+ "men",
+ "game",
+ "ball",
+ "basketball",
+ "diversity"
+ ],
+ "moji": "⛹"
},
"basketball_player_tone1": {
"unicode": "26F9-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person with ball tone 1",
"shortname": ":basketball_player_tone1:",
"category": "activity",
@@ -1607,11 +1640,19 @@
":person_with_ball_tone1:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "sport",
+ "travel",
+ "men",
+ "game",
+ "ball",
+ "basketball"
+ ],
+ "moji": "⛹🏻"
},
"basketball_player_tone2": {
"unicode": "26F9-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person with ball tone 2",
"shortname": ":basketball_player_tone2:",
"category": "activity",
@@ -1619,11 +1660,19 @@
":person_with_ball_tone2:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "sport",
+ "travel",
+ "men",
+ "game",
+ "ball",
+ "basketball"
+ ],
+ "moji": "⛹🏼"
},
"basketball_player_tone3": {
"unicode": "26F9-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person with ball tone 3",
"shortname": ":basketball_player_tone3:",
"category": "activity",
@@ -1631,11 +1680,19 @@
":person_with_ball_tone3:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "sport",
+ "travel",
+ "men",
+ "game",
+ "ball",
+ "basketball"
+ ],
+ "moji": "⛹🏽"
},
"basketball_player_tone4": {
"unicode": "26F9-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person with ball tone 4",
"shortname": ":basketball_player_tone4:",
"category": "activity",
@@ -1643,11 +1700,19 @@
":person_with_ball_tone4:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "sport",
+ "travel",
+ "men",
+ "game",
+ "ball",
+ "basketball"
+ ],
+ "moji": "⛹🏾"
},
"basketball_player_tone5": {
"unicode": "26F9-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person with ball tone 5",
"shortname": ":basketball_player_tone5:",
"category": "activity",
@@ -1655,14 +1720,33 @@
":person_with_ball_tone5:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "sport",
+ "travel",
+ "men",
+ "game",
+ "ball",
+ "basketball"
+ ],
+ "moji": "⛹🏿"
+ },
+ "bat": {
+ "unicode": "1F987",
+ "unicode_alternates": [],
+ "name": "bat",
+ "shortname": ":bat:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🦇"
},
"bath": {
"unicode": "1F6C0",
"unicode_alternates": [],
"name": "bath",
"shortname": ":bath:",
- "category": "objects",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -1677,16 +1761,17 @@
"bathroom",
"soap",
"water",
- "clean",
"shampoo",
"lather",
- "water"
+ "tired",
+ "diversity",
+ "steam"
],
"moji": "🛀"
},
"bath_tone1": {
"unicode": "1F6C0-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "bath tone 1",
"shortname": ":bath_tone1:",
"category": "activity",
@@ -1705,11 +1790,12 @@
"clean",
"shampoo",
"lather"
- ]
+ ],
+ "moji": "🛀🏻"
},
"bath_tone2": {
"unicode": "1F6C0-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "bath tone 2",
"shortname": ":bath_tone2:",
"category": "activity",
@@ -1728,11 +1814,12 @@
"clean",
"shampoo",
"lather"
- ]
+ ],
+ "moji": "🛀🏼"
},
"bath_tone3": {
"unicode": "1F6C0-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "bath tone 3",
"shortname": ":bath_tone3:",
"category": "activity",
@@ -1751,11 +1838,12 @@
"clean",
"shampoo",
"lather"
- ]
+ ],
+ "moji": "🛀🏽"
},
"bath_tone4": {
"unicode": "1F6C0-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "bath tone 4",
"shortname": ":bath_tone4:",
"category": "activity",
@@ -1774,11 +1862,12 @@
"clean",
"shampoo",
"lather"
- ]
+ ],
+ "moji": "🛀🏾"
},
"bath_tone5": {
"unicode": "1F6C0-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "bath tone 5",
"shortname": ":bath_tone5:",
"category": "activity",
@@ -1797,7 +1886,8 @@
"clean",
"shampoo",
"lather"
- ]
+ ],
+ "moji": "🛀🏿"
},
"bathtub": {
"unicode": "1F6C1",
@@ -1819,10 +1909,11 @@
"bathroom",
"soap",
"water",
- "clean",
"shampoo",
"lather",
- "water"
+ "object",
+ "tired",
+ "steam"
],
"moji": "🛁"
},
@@ -1837,7 +1928,8 @@
"keywords": [
"energy",
"power",
- "sustain"
+ "sustain",
+ "object"
],
"moji": "🔋"
},
@@ -1846,7 +1938,7 @@
"unicode_alternates": [],
"name": "beach with umbrella",
"shortname": ":beach:",
- "category": "travel_places",
+ "category": "travel",
"aliases": [
":beach_with_umbrella:"
],
@@ -1859,12 +1951,18 @@
"relaxation",
"tanning",
"tan",
- "swimming"
- ]
+ "swimming",
+ "places",
+ "travel",
+ "tropical",
+ "beach",
+ "swim"
+ ],
+ "moji": "🏖"
},
"beach_umbrella": {
"unicode": "26F1",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "umbrella on ground",
"shortname": ":beach_umbrella:",
"category": "objects",
@@ -1877,8 +1975,11 @@
"rain",
"sun",
"travel",
- "weather"
- ]
+ "weather",
+ "vacation",
+ "tropical"
+ ],
+ "moji": "⛱"
},
"bear": {
"unicode": "1F43B",
@@ -1890,7 +1991,9 @@
"aliases_ascii": [],
"keywords": [
"animal",
- "nature"
+ "nature",
+ "wildlife",
+ "roar"
],
"moji": "🐻"
},
@@ -1899,7 +2002,7 @@
"unicode_alternates": [],
"name": "bed",
"shortname": ":bed:",
- "category": "travel_places",
+ "category": "objects",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -1909,8 +2012,11 @@
"full",
"twin",
"king",
- "mattress"
- ]
+ "mattress",
+ "object",
+ "tired"
+ ],
+ "moji": "🛏"
},
"bee": {
"unicode": "1F41D",
@@ -1932,7 +2038,8 @@
"honey",
"hive",
"bumble",
- "pollination"
+ "pollination",
+ "insects"
],
"moji": "🐝"
},
@@ -1941,7 +2048,7 @@
"unicode_alternates": [],
"name": "beer mug",
"shortname": ":beer:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -1962,7 +2069,9 @@
"brewery",
"micro",
"pint",
- "boot"
+ "boot",
+ "alcohol",
+ "parties"
],
"moji": "🍺"
},
@@ -1971,7 +2080,7 @@
"unicode_alternates": [],
"name": "clinking beer mugs",
"shortname": ":beers:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -1987,11 +2096,14 @@
"mug",
"toast",
"celebrate",
- "pub",
"bar",
"jolly",
"hops",
- "clink"
+ "clink",
+ "alcohol",
+ "thank you",
+ "boys night",
+ "parties"
],
"moji": "🍻"
},
@@ -2013,8 +2125,9 @@
"beetle",
"cow",
"lady cow",
- "insect",
- "endearment"
+ "endearment",
+ "insects",
+ "animal"
],
"moji": "🐞"
},
@@ -2023,12 +2136,13 @@
"unicode_alternates": [],
"name": "japanese symbol for beginner",
"shortname": ":beginner:",
- "category": "places",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"badge",
- "shield"
+ "shield",
+ "symbol"
],
"moji": "🔰"
},
@@ -2037,7 +2151,7 @@
"unicode_alternates": [],
"name": "bell",
"shortname": ":bell:",
- "category": "objects",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -2045,7 +2159,10 @@
"christmas",
"notification",
"sound",
- "xmas"
+ "xmas",
+ "object",
+ "alarm",
+ "symbol"
],
"moji": "🔔"
},
@@ -2054,7 +2171,7 @@
"unicode_alternates": [],
"name": "bellhop bell",
"shortname": ":bellhop:",
- "category": "travel_places",
+ "category": "objects",
"aliases": [
":bellhop_bell:"
],
@@ -2062,15 +2179,17 @@
"keywords": [
"hotel",
"porter",
- "ding"
- ]
+ "ding",
+ "object"
+ ],
+ "moji": "🛎"
},
"bento": {
"unicode": "1F371",
"unicode_alternates": [],
"name": "bento box",
"shortname": ":bento:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -2078,13 +2197,14 @@
"food",
"japanese",
"bento",
- "japanese",
"rice",
"meal",
- "box",
"obento",
"convenient",
- "lunchbox"
+ "lunchbox",
+ "object",
+ "sushi",
+ "japan"
],
"moji": "🍱"
},
@@ -2093,7 +2213,7 @@
"unicode_alternates": [],
"name": "bicyclist",
"shortname": ":bicyclist:",
- "category": "objects",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -2103,16 +2223,19 @@
"sports",
"bicyclist",
"road",
- "bike",
"pedal",
"bicycle",
- "transportation"
+ "transportation",
+ "men",
+ "workout",
+ "sport",
+ "diversity"
],
"moji": "🚴"
},
"bicyclist_tone1": {
"unicode": "1F6B4-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "bicyclist tone 1",
"shortname": ":bicyclist_tone1:",
"category": "activity",
@@ -2127,11 +2250,12 @@
"pedal",
"bicycle",
"transportation"
- ]
+ ],
+ "moji": "🚴🏻"
},
"bicyclist_tone2": {
"unicode": "1F6B4-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "bicyclist tone 2",
"shortname": ":bicyclist_tone2:",
"category": "activity",
@@ -2146,11 +2270,12 @@
"pedal",
"bicycle",
"transportation"
- ]
+ ],
+ "moji": "🚴🏼"
},
"bicyclist_tone3": {
"unicode": "1F6B4-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "bicyclist tone 3",
"shortname": ":bicyclist_tone3:",
"category": "activity",
@@ -2165,11 +2290,12 @@
"pedal",
"bicycle",
"transportation"
- ]
+ ],
+ "moji": "🚴🏽"
},
"bicyclist_tone4": {
"unicode": "1F6B4-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "bicyclist tone 4",
"shortname": ":bicyclist_tone4:",
"category": "activity",
@@ -2184,11 +2310,12 @@
"pedal",
"bicycle",
"transportation"
- ]
+ ],
+ "moji": "🚴🏾"
},
"bicyclist_tone5": {
"unicode": "1F6B4-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "bicyclist tone 5",
"shortname": ":bicyclist_tone5:",
"category": "activity",
@@ -2203,14 +2330,15 @@
"pedal",
"bicycle",
"transportation"
- ]
+ ],
+ "moji": "🚴🏿"
},
"bike": {
"unicode": "1F6B2",
"unicode_alternates": [],
"name": "bicycle",
"shortname": ":bike:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -2220,8 +2348,8 @@
"sports",
"bike",
"pedal",
- "bicycle",
- "transportation"
+ "transportation",
+ "travel"
],
"moji": "🚲"
},
@@ -2230,7 +2358,7 @@
"unicode_alternates": [],
"name": "bikini",
"shortname": ":bikini:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -2239,13 +2367,18 @@
"female",
"girl",
"swimming",
- "woman"
+ "woman",
+ "women",
+ "sexy",
+ "vacation",
+ "tropical",
+ "swim"
],
"moji": "👙"
},
"biohazard": {
"unicode": "2623",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "biohazard sign",
"shortname": ":biohazard:",
"category": "symbols",
@@ -2254,8 +2387,10 @@
],
"aliases_ascii": [],
"keywords": [
- "symbol"
- ]
+ "symbol",
+ "science"
+ ],
+ "moji": "☣"
},
"bird": {
"unicode": "1F426",
@@ -2269,7 +2404,8 @@
"animal",
"fly",
"nature",
- "tweet"
+ "tweet",
+ "wildlife"
],
"moji": "🐦"
},
@@ -2278,7 +2414,7 @@
"unicode_alternates": [],
"name": "birthday cake",
"shortname": ":birthday:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -2286,10 +2422,11 @@
"party",
"birthday",
"birth",
- "cake",
"dessert",
"wish",
- "celebrate"
+ "celebrate",
+ "food",
+ "parties"
],
"moji": "🎂"
},
@@ -2300,26 +2437,42 @@
],
"name": "medium black circle",
"shortname": ":black_circle:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "shape"
+ "shape",
+ "shapes",
+ "symbol",
+ "circle"
],
"moji": "⚫"
},
+ "black_heart": {
+ "unicode": "1F5A4",
+ "unicode_alternates": [],
+ "name": "black heart",
+ "shortname": ":black_heart:",
+ "category": "symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🖤"
+ },
"black_joker": {
"unicode": "1F0CF",
"unicode_alternates": [],
"name": "playing card black joker",
"shortname": ":black_joker:",
- "category": "objects",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"cards",
"game",
- "poker"
+ "poker",
+ "object",
+ "symbol"
],
"moji": "🃏"
},
@@ -2330,11 +2483,14 @@
],
"name": "black large square",
"shortname": ":black_large_square:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "shape"
+ "shape",
+ "shapes",
+ "symbol",
+ "square"
],
"moji": "⬛"
},
@@ -2345,10 +2501,14 @@
],
"name": "black medium small square",
"shortname": ":black_medium_small_square:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": [],
+ "keywords": [
+ "shapes",
+ "symbol",
+ "square"
+ ],
"moji": "◾"
},
"black_medium_square": {
@@ -2358,11 +2518,14 @@
],
"name": "black medium square",
"shortname": ":black_medium_square:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "shape"
+ "shape",
+ "shapes",
+ "symbol",
+ "square"
],
"moji": "◼"
},
@@ -2378,7 +2541,10 @@
"aliases_ascii": [],
"keywords": [
"pen",
- "stationery"
+ "stationery",
+ "object",
+ "office",
+ "write"
],
"moji": "✒"
},
@@ -2389,10 +2555,14 @@
],
"name": "black small square",
"shortname": ":black_small_square:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": [],
+ "keywords": [
+ "shapes",
+ "symbol",
+ "square"
+ ],
"moji": "▪"
},
"black_square_button": {
@@ -2400,11 +2570,14 @@
"unicode_alternates": [],
"name": "black square button",
"shortname": ":black_square_button:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "frame"
+ "frame",
+ "shapes",
+ "symbol",
+ "square"
],
"moji": "🔲"
},
@@ -2422,7 +2595,8 @@
"yellow",
"blossom",
"daisy",
- "flower"
+ "flower",
+ "plant"
],
"moji": "🌼"
},
@@ -2445,7 +2619,9 @@
"ballonfish",
"toadfish",
"fugu fish",
- "sushi"
+ "sushi",
+ "wildlife",
+ "animal"
],
"moji": "🐡"
},
@@ -2460,7 +2636,11 @@
"keywords": [
"knowledge",
"library",
- "read"
+ "read",
+ "object",
+ "office",
+ "write",
+ "book"
],
"moji": "📘"
},
@@ -2469,15 +2649,16 @@
"unicode_alternates": [],
"name": "recreational vehicle",
"shortname": ":blue_car:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"car",
"suv",
- "car",
"wagon",
- "automobile"
+ "automobile",
+ "transportation",
+ "travel"
],
"moji": "🚙"
},
@@ -2486,7 +2667,7 @@
"unicode_alternates": [],
"name": "blue heart",
"shortname": ":blue_heart:",
- "category": "emoticons",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -2496,11 +2677,11 @@
"valentines",
"blue",
"heart",
- "love",
"stability",
"truth",
"loyalty",
- "trust"
+ "trust",
+ "symbol"
],
"moji": "💙"
},
@@ -2509,7 +2690,7 @@
"unicode_alternates": [],
"name": "smiling face with smiling eyes",
"shortname": ":blush:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -2521,8 +2702,10 @@
"shy",
"smile",
"smiling",
- "smile",
- "smiley"
+ "smiley",
+ "emotion",
+ "good",
+ "beautiful"
],
"moji": "😊"
},
@@ -2536,7 +2719,8 @@
"aliases_ascii": [],
"keywords": [
"animal",
- "nature"
+ "nature",
+ "wildlife"
],
"moji": "🐗"
},
@@ -2550,7 +2734,11 @@
"aliases_ascii": [],
"keywords": [
"boom",
- "explode"
+ "explode",
+ "object",
+ "weapon",
+ "dead",
+ "blast"
],
"moji": "💣"
},
@@ -2564,26 +2752,14 @@
"aliases_ascii": [],
"keywords": [
"library",
- "literature"
+ "literature",
+ "object",
+ "office",
+ "write",
+ "book"
],
"moji": "📖"
},
- "book2": {
- "unicode": "1F56E",
- "unicode_alternates": [],
- "name": "book",
- "shortname": ":book2:",
- "category": "objects_symbols",
- "aliases": [],
- "aliases_ascii": [],
- "keywords": [
- "library",
- "literature",
- "novel",
- "reading",
- "story"
- ]
- },
"bookmark": {
"unicode": "1F516",
"unicode_alternates": [],
@@ -2593,7 +2769,9 @@
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "favorite"
+ "favorite",
+ "object",
+ "book"
],
"moji": "🔖"
},
@@ -2606,7 +2784,9 @@
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "favorite"
+ "favorite",
+ "office",
+ "write"
],
"moji": "📑"
},
@@ -2620,7 +2800,11 @@
"aliases_ascii": [],
"keywords": [
"library",
- "literature"
+ "literature",
+ "object",
+ "office",
+ "write",
+ "book"
],
"moji": "📚"
},
@@ -2629,7 +2813,7 @@
"unicode_alternates": [],
"name": "collision symbol",
"shortname": ":boom:",
- "category": "emoticons",
+ "category": "nature",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -2642,7 +2826,9 @@
"fire",
"emphasis",
"wow",
- "bam"
+ "bam",
+ "symbol",
+ "blast"
],
"moji": "💥"
},
@@ -2651,12 +2837,16 @@
"unicode_alternates": [],
"name": "womans boots",
"shortname": ":boot:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"fashion",
- "shoes"
+ "shoes",
+ "women",
+ "shoe",
+ "sexy",
+ "accessories"
],
"moji": "👢"
},
@@ -2670,33 +2860,20 @@
"aliases_ascii": [],
"keywords": [
"flowers",
- "nature"
+ "nature",
+ "flower",
+ "plant",
+ "rip",
+ "condolence"
],
"moji": "💐"
},
- "bouquet2": {
- "unicode": "1F395",
- "unicode_alternates": [],
- "name": "bouquet of flowers",
- "shortname": ":bouquet2:",
- "category": "celebration",
- "aliases": [
- ":bouquet_of_flowers:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "nature",
- "marriage",
- "wedding",
- "bride"
- ]
- },
"bow": {
"unicode": "1F647",
"unicode_alternates": [],
"name": "person bowing deeply",
"shortname": ":bow:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -2707,13 +2884,16 @@
"bow",
"respect",
"curtsy",
- "bend"
+ "bend",
+ "people",
+ "pray",
+ "diversity"
],
"moji": "🙇"
},
"bow_and_arrow": {
"unicode": "1F3F9",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "bow and arrow",
"shortname": ":bow_and_arrow:",
"category": "activity",
@@ -2721,11 +2901,15 @@
":archery:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "weapon",
+ "sport"
+ ],
+ "moji": "🏹"
},
"bow_tone1": {
"unicode": "1F647-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person bowing deeply tone 1",
"shortname": ":bow_tone1:",
"category": "people",
@@ -2739,11 +2923,12 @@
"bow",
"respect",
"bend"
- ]
+ ],
+ "moji": "🙇🏻"
},
"bow_tone2": {
"unicode": "1F647-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person bowing deeply tone 2",
"shortname": ":bow_tone2:",
"category": "people",
@@ -2757,11 +2942,12 @@
"bow",
"respect",
"bend"
- ]
+ ],
+ "moji": "🙇🏼"
},
"bow_tone3": {
"unicode": "1F647-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person bowing deeply tone 3",
"shortname": ":bow_tone3:",
"category": "people",
@@ -2775,11 +2961,12 @@
"bow",
"respect",
"bend"
- ]
+ ],
+ "moji": "🙇🏽"
},
"bow_tone4": {
"unicode": "1F647-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person bowing deeply tone 4",
"shortname": ":bow_tone4:",
"category": "people",
@@ -2793,11 +2980,12 @@
"bow",
"respect",
"bend"
- ]
+ ],
+ "moji": "🙇🏾"
},
"bow_tone5": {
"unicode": "1F647-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person bowing deeply tone 5",
"shortname": ":bow_tone5:",
"category": "people",
@@ -2811,14 +2999,15 @@
"bow",
"respect",
"bend"
- ]
+ ],
+ "moji": "🙇🏿"
},
"bowling": {
"unicode": "1F3B3",
"unicode_alternates": [],
"name": "bowling",
"shortname": ":bowling:",
- "category": "objects",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -2831,28 +3020,46 @@
"pin",
"strike",
"spare",
- "game"
+ "game",
+ "sport",
+ "boys night"
],
"moji": "🎳"
},
+ "boxing_glove": {
+ "unicode": "1F94A",
+ "unicode_alternates": [],
+ "name": "boxing glove",
+ "shortname": ":boxing_glove:",
+ "category": "activity",
+ "aliases": [
+ ":boxing_gloves:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🥊"
+ },
"boy": {
"unicode": "1F466",
"unicode_alternates": [],
"name": "boy",
"shortname": ":boy:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"guy",
"male",
- "man"
+ "man",
+ "people",
+ "baby",
+ "diversity"
],
"moji": "👦"
},
"boy_tone1": {
"unicode": "1F466-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "boy tone 1",
"shortname": ":boy_tone1:",
"category": "people",
@@ -2862,11 +3069,12 @@
"male",
"kid",
"child"
- ]
+ ],
+ "moji": "👦🏻"
},
"boy_tone2": {
"unicode": "1F466-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "boy tone 2",
"shortname": ":boy_tone2:",
"category": "people",
@@ -2876,11 +3084,12 @@
"male",
"kid",
"child"
- ]
+ ],
+ "moji": "👦🏼"
},
"boy_tone3": {
"unicode": "1F466-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "boy tone 3",
"shortname": ":boy_tone3:",
"category": "people",
@@ -2890,11 +3099,12 @@
"male",
"kid",
"child"
- ]
+ ],
+ "moji": "👦🏽"
},
"boy_tone4": {
"unicode": "1F466-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "boy tone 4",
"shortname": ":boy_tone4:",
"category": "people",
@@ -2904,11 +3114,12 @@
"male",
"kid",
"child"
- ]
+ ],
+ "moji": "👦🏾"
},
"boy_tone5": {
"unicode": "1F466-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "boy tone 5",
"shortname": ":boy_tone5:",
"category": "people",
@@ -2918,27 +3129,15 @@
"male",
"kid",
"child"
- ]
- },
- "boys_symbol": {
- "unicode": "1F6C9",
- "unicode_alternates": [],
- "name": "boys symbol",
- "shortname": ":boys_symbol:",
- "category": "objects_symbols",
- "aliases": [],
- "aliases_ascii": [],
- "keywords": [
- "male",
- "child"
- ]
+ ],
+ "moji": "👦🏿"
},
"bread": {
"unicode": "1F35E",
"unicode_alternates": [],
"name": "bread",
"shortname": ":bread:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -2957,7 +3156,7 @@
"unicode_alternates": [],
"name": "bride with veil",
"shortname": ":bride_with_veil:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -2965,19 +3164,21 @@
"marriage",
"wedding",
"bride",
- "wedding",
"planning",
"veil",
"gown",
"dress",
"engagement",
- "white"
+ "white",
+ "people",
+ "women",
+ "diversity"
],
"moji": "👰"
},
"bride_with_veil_tone1": {
"unicode": "1F470-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "bride with veil tone 1",
"shortname": ":bride_with_veil_tone1:",
"category": "people",
@@ -2987,17 +3188,17 @@
"couple",
"marriage",
"wedding",
- "wedding",
"planning",
"gown",
"dress",
"engagement",
"white"
- ]
+ ],
+ "moji": "👰🏻"
},
"bride_with_veil_tone2": {
"unicode": "1F470-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "bride with veil tone 2",
"shortname": ":bride_with_veil_tone2:",
"category": "people",
@@ -3007,17 +3208,17 @@
"couple",
"marriage",
"wedding",
- "wedding",
"planning",
"gown",
"dress",
"engagement",
"white"
- ]
+ ],
+ "moji": "👰🏼"
},
"bride_with_veil_tone3": {
"unicode": "1F470-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "bride with veil tone 3",
"shortname": ":bride_with_veil_tone3:",
"category": "people",
@@ -3027,17 +3228,17 @@
"couple",
"marriage",
"wedding",
- "wedding",
"planning",
"gown",
"dress",
"engagement",
"white"
- ]
+ ],
+ "moji": "👰🏽"
},
"bride_with_veil_tone4": {
"unicode": "1F470-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "bride with veil tone 4",
"shortname": ":bride_with_veil_tone4:",
"category": "people",
@@ -3047,17 +3248,17 @@
"couple",
"marriage",
"wedding",
- "wedding",
"planning",
"gown",
"dress",
"engagement",
"white"
- ]
+ ],
+ "moji": "👰🏾"
},
"bride_with_veil_tone5": {
"unicode": "1F470-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "bride with veil tone 5",
"shortname": ":bride_with_veil_tone5:",
"category": "people",
@@ -3067,20 +3268,20 @@
"couple",
"marriage",
"wedding",
- "wedding",
"planning",
"gown",
"dress",
"engagement",
"white"
- ]
+ ],
+ "moji": "👰🏿"
},
"bridge_at_night": {
"unicode": "1F309",
"unicode_alternates": [],
"name": "bridge at night",
"shortname": ":bridge_at_night:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -3093,7 +3294,11 @@
"evening",
"suspension",
"golden",
- "gate"
+ "gate",
+ "places",
+ "travel",
+ "vacation",
+ "goodnight"
],
"moji": "🌉"
},
@@ -3102,13 +3307,17 @@
"unicode_alternates": [],
"name": "briefcase",
"shortname": ":briefcase:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"business",
"documents",
- "work"
+ "work",
+ "bag",
+ "accessories",
+ "nutcase",
+ "job"
],
"moji": "💼"
},
@@ -3117,14 +3326,17 @@
"unicode_alternates": [],
"name": "broken heart",
"shortname": ":broken_heart:",
- "category": "emoticons",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [
"</3"
],
"keywords": [
"sad",
- "sorry"
+ "sorry",
+ "love",
+ "symbol",
+ "heartbreak"
],
"moji": "💔"
},
@@ -3140,9 +3352,10 @@
"insect",
"nature",
"bug",
- "insect",
"virus",
- "error"
+ "error",
+ "insects",
+ "animal"
],
"moji": "🐛"
},
@@ -3159,7 +3372,8 @@
"light",
"idea",
"bulb",
- "light"
+ "object",
+ "science"
],
"moji": "💡"
},
@@ -3168,14 +3382,15 @@
"unicode_alternates": [],
"name": "high-speed train with bullet nose",
"shortname": ":bullettrain_front:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"transportation",
"train",
"bullet",
- "rail"
+ "rail",
+ "travel"
],
"moji": "🚅"
},
@@ -3184,7 +3399,7 @@
"unicode_alternates": [],
"name": "high-speed train",
"shortname": ":bullettrain_side:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -3192,58 +3407,31 @@
"vehicle",
"train",
"bullet",
- "rail"
+ "rail",
+ "travel"
],
"moji": "🚄"
},
- "bullhorn": {
- "unicode": "1F56B",
- "unicode_alternates": [],
- "name": "bullhorn",
- "shortname": ":bullhorn:",
- "category": "objects_symbols",
- "aliases": [],
- "aliases_ascii": [],
- "keywords": [
- "sound",
- "noise",
- "announcement",
- "megaphone"
- ]
- },
- "bullhorn_waves": {
- "unicode": "1F56C",
- "unicode_alternates": [],
- "name": "bullhorn with sound waves",
- "shortname": ":bullhorn_waves:",
- "category": "objects_symbols",
- "aliases": [
- ":bullhorn_with_sound_waves:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "sound",
- "noise",
- "announcement",
- "megaphone"
- ]
- },
"burrito": {
"unicode": "1F32F",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "burrito",
"shortname": ":burrito:",
- "category": "foods",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "food",
+ "mexican"
+ ],
+ "moji": "🌯"
},
"bus": {
"unicode": "1F68C",
"unicode_alternates": [],
"name": "bus",
"shortname": ":bus:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -3253,8 +3441,8 @@
"bus",
"school",
"city",
- "transportation",
- "public"
+ "public",
+ "office"
],
"moji": "🚌"
},
@@ -3263,7 +3451,7 @@
"unicode_alternates": [],
"name": "bus stop",
"shortname": ":busstop:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -3272,7 +3460,7 @@
"stop",
"city",
"transport",
- "transportation"
+ "object"
],
"moji": "🚏"
},
@@ -3281,7 +3469,7 @@
"unicode_alternates": [],
"name": "bust in silhouette",
"shortname": ":bust_in_silhouette:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -3290,8 +3478,6 @@
"person",
"user",
"silhouette",
- "person",
- "user",
"member",
"account",
"guest",
@@ -3300,7 +3486,8 @@
"profile",
"me",
"myself",
- "i"
+ "i",
+ "people"
],
"moji": "👤"
},
@@ -3309,7 +3496,7 @@
"unicode_alternates": [],
"name": "busts in silhouette",
"shortname": ":busts_in_silhouette:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -3322,7 +3509,6 @@
"silhouette",
"silhouettes",
"people",
- "user",
"members",
"accounts",
"relationship",
@@ -3330,6 +3516,17 @@
],
"moji": "👥"
},
+ "butterfly": {
+ "unicode": "1F98B",
+ "unicode_alternates": [],
+ "name": "butterfly",
+ "shortname": ":butterfly:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🦋"
+ },
"cactus": {
"unicode": "1F335",
"unicode_alternates": [],
@@ -3346,7 +3543,8 @@
"desert",
"drought",
"spike",
- "poke"
+ "poke",
+ "trees"
],
"moji": "🌵"
},
@@ -3355,7 +3553,7 @@
"unicode_alternates": [],
"name": "shortcake",
"shortname": ":cake:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -3368,24 +3566,6 @@
],
"moji": "🍰"
},
- "calculator": {
- "unicode": "1F5A9",
- "unicode_alternates": [],
- "name": "pocket calculator",
- "shortname": ":calculator:",
- "category": "objects_symbols",
- "aliases": [
- ":pocket calculator:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "add",
- "subtract",
- "multiple",
- "divide",
- "scientific"
- ]
- },
"calendar": {
"unicode": "1F4C6",
"unicode_alternates": [],
@@ -3395,7 +3575,9 @@
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "schedule"
+ "schedule",
+ "object",
+ "office"
],
"moji": "📆"
},
@@ -3404,7 +3586,7 @@
"unicode_alternates": [],
"name": "spiral calendar pad",
"shortname": ":calendar_spiral:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [
":spiral_calendar_pad:"
],
@@ -3412,8 +3594,89 @@
"keywords": [
"schedule",
"date",
- "day"
- ]
+ "day",
+ "object",
+ "office"
+ ],
+ "moji": "🗓"
+ },
+ "call_me": {
+ "unicode": "1F919",
+ "unicode_alternates": [],
+ "name": "call me hand",
+ "shortname": ":call_me:",
+ "category": "people",
+ "aliases": [
+ ":call_me_hand:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤙"
+ },
+ "call_me_tone1": {
+ "unicode": "1F919-1F3FB",
+ "unicode_alternates": [],
+ "name": "call me hand tone 1",
+ "shortname": ":call_me_tone1:",
+ "category": "people",
+ "aliases": [
+ ":call_me_hand_tone1:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤙🏻"
+ },
+ "call_me_tone2": {
+ "unicode": "1F919-1F3FC",
+ "unicode_alternates": [],
+ "name": "call me hand tone 2",
+ "shortname": ":call_me_tone2:",
+ "category": "people",
+ "aliases": [
+ ":call_me_hand_tone2:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤙🏼"
+ },
+ "call_me_tone3": {
+ "unicode": "1F919-1F3FD",
+ "unicode_alternates": [],
+ "name": "call me hand tone 3",
+ "shortname": ":call_me_tone3:",
+ "category": "people",
+ "aliases": [
+ ":call_me_hand_tone3:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤙🏽"
+ },
+ "call_me_tone4": {
+ "unicode": "1F919-1F3FE",
+ "unicode_alternates": [],
+ "name": "call me hand tone 4",
+ "shortname": ":call_me_tone4:",
+ "category": "people",
+ "aliases": [
+ ":call_me_hand_tone4:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤙🏾"
+ },
+ "call_me_tone5": {
+ "unicode": "1F919-1F3FF",
+ "unicode_alternates": [],
+ "name": "call me hand tone 5",
+ "shortname": ":call_me_tone5:",
+ "category": "people",
+ "aliases": [
+ ":call_me_hand_tone5:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤙🏿"
},
"calling": {
"unicode": "1F4F2",
@@ -3425,7 +3688,10 @@
"aliases_ascii": [],
"keywords": [
"incoming",
- "iphone"
+ "iphone",
+ "electronics",
+ "phone",
+ "selfie"
],
"moji": "📲"
},
@@ -3447,11 +3713,11 @@
"desert",
"central asia",
"heat",
- "hot",
"water",
"hump day",
"wednesday",
- "sex"
+ "sex",
+ "wildlife"
],
"moji": "🐫"
},
@@ -3465,7 +3731,10 @@
"aliases_ascii": [],
"keywords": [
"gadgets",
- "photo"
+ "photo",
+ "electronics",
+ "camera",
+ "selfie"
],
"moji": "📷"
},
@@ -3474,20 +3743,23 @@
"unicode_alternates": [],
"name": "camera with flash",
"shortname": ":camera_with_flash:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"photo",
- "picture"
- ]
+ "picture",
+ "electronics",
+ "camera"
+ ],
+ "moji": "📸"
},
"camping": {
"unicode": "1F3D5",
"unicode_alternates": [],
"name": "camping",
"shortname": ":camping:",
- "category": "travel_places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -3495,22 +3767,13 @@
"nature",
"wilderness",
"roughing",
- "activity"
- ]
- },
- "cancellation_x": {
- "unicode": "1F5D9",
- "unicode_alternates": [],
- "name": "cancellation x",
- "shortname": ":cancellation_x:",
- "category": "objects_symbols",
- "aliases": [],
- "aliases_ascii": [],
- "keywords": [
- "cancel",
- "stop",
- "delete"
- ]
+ "activity",
+ "places",
+ "travel",
+ "vacation",
+ "camp"
+ ],
+ "moji": "🏕"
},
"cancer": {
"unicode": "264B",
@@ -3519,7 +3782,7 @@
],
"name": "cancer",
"shortname": ":cancer:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -3531,9 +3794,8 @@
"stars",
"zodiac",
"sign",
- "sign",
- "zodiac",
- "horoscope"
+ "horoscope",
+ "symbol"
],
"moji": "♋"
},
@@ -3542,20 +3804,22 @@
"unicode_alternates": [],
"name": "candle",
"shortname": ":candle:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"light",
- "wax"
- ]
+ "wax",
+ "object"
+ ],
+ "moji": "🕯"
},
"candy": {
"unicode": "1F36C",
"unicode_alternates": [],
"name": "candy",
"shortname": ":candy:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -3564,22 +3828,38 @@
"candy",
"sugar",
"sweet",
- "hard"
+ "hard",
+ "food",
+ "halloween"
],
"moji": "🍬"
},
+ "canoe": {
+ "unicode": "1F6F6",
+ "unicode_alternates": [],
+ "name": "canoe",
+ "shortname": ":canoe:",
+ "category": "travel",
+ "aliases": [
+ ":kayak:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🛶"
+ },
"capital_abcd": {
"unicode": "1F520",
"unicode_alternates": [],
"name": "input symbol for latin capital letters",
"shortname": ":capital_abcd:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"alphabet",
"blue-square",
- "words"
+ "words",
+ "symbol"
],
"moji": "🔠"
},
@@ -3590,7 +3870,7 @@
],
"name": "capricorn",
"shortname": ":capricorn:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -3603,9 +3883,8 @@
"stars",
"zodiac",
"sign",
- "sign",
- "zodiac",
- "horoscope"
+ "horoscope",
+ "symbol"
],
"moji": "♑"
},
@@ -3614,15 +3893,19 @@
"unicode_alternates": [],
"name": "card file box",
"shortname": ":card_box:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [
":card_file_box:"
],
"aliases_ascii": [],
"keywords": [
"index",
- "organization"
- ]
+ "organization",
+ "object",
+ "work",
+ "office"
+ ],
+ "moji": "🗃"
},
"card_index": {
"unicode": "1F4C7",
@@ -3634,7 +3917,10 @@
"aliases_ascii": [],
"keywords": [
"business",
- "stationery"
+ "stationery",
+ "object",
+ "work",
+ "office"
],
"moji": "📇"
},
@@ -3643,7 +3929,7 @@
"unicode_alternates": [],
"name": "carousel horse",
"shortname": ":carousel_horse:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -3651,37 +3937,106 @@
"horse",
"photo",
"carousel",
- "horse",
"amusement",
"park",
"ride",
"entertainment",
- "park",
- "fair"
+ "fair",
+ "places",
+ "object",
+ "vacation",
+ "roller coaster"
],
"moji": "🎠"
},
- "cartridge": {
- "unicode": "1F5AD",
+ "carrot": {
+ "unicode": "1F955",
"unicode_alternates": [],
- "name": "tape cartridge",
- "shortname": ":cartridge:",
- "category": "objects_symbols",
+ "name": "carrot",
+ "shortname": ":carrot:",
+ "category": "food",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🥕"
+ },
+ "cartwheel": {
+ "unicode": "1F938",
+ "unicode_alternates": [],
+ "name": "person doing cartwheel",
+ "shortname": ":cartwheel:",
+ "category": "activity",
"aliases": [
- ":tape_cartridge:"
+ ":person_doing_cartwheel:"
],
"aliases_ascii": [],
- "keywords": [
- "oldschool",
- "save",
- "technology",
- "disk",
- "storage",
- "information",
- "computer",
- "drive",
- "megabyte"
- ]
+ "keywords": [],
+ "moji": "🤸"
+ },
+ "cartwheel_tone1": {
+ "unicode": "1F938-1F3FB",
+ "unicode_alternates": [],
+ "name": "person doing cartwheel tone 1",
+ "shortname": ":cartwheel_tone1:",
+ "category": "activity",
+ "aliases": [
+ ":person_doing_cartwheel_tone1:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤸🏻"
+ },
+ "cartwheel_tone2": {
+ "unicode": "1F938-1F3FC",
+ "unicode_alternates": [],
+ "name": "person doing cartwheel tone 2",
+ "shortname": ":cartwheel_tone2:",
+ "category": "activity",
+ "aliases": [
+ ":person_doing_cartwheel_tone2:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤸🏼"
+ },
+ "cartwheel_tone3": {
+ "unicode": "1F938-1F3FD",
+ "unicode_alternates": [],
+ "name": "person doing cartwheel tone 3",
+ "shortname": ":cartwheel_tone3:",
+ "category": "activity",
+ "aliases": [
+ ":person_doing_cartwheel_tone3:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤸🏽"
+ },
+ "cartwheel_tone4": {
+ "unicode": "1F938-1F3FE",
+ "unicode_alternates": [],
+ "name": "person doing cartwheel tone 4",
+ "shortname": ":cartwheel_tone4:",
+ "category": "activity",
+ "aliases": [
+ ":person_doing_cartwheel_tone4:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤸🏾,"
+ },
+ "cartwheel_tone5": {
+ "unicode": "1F938-1F3FF",
+ "unicode_alternates": [],
+ "name": "person doing cartwheel tone 5",
+ "shortname": ":cartwheel_tone5:",
+ "category": "activity",
+ "aliases": [
+ ":person_doing_cartwheel_tone5:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤸🏿"
},
"cat": {
"unicode": "1F431",
@@ -3693,7 +4048,10 @@
"aliases_ascii": [],
"keywords": [
"animal",
- "meow"
+ "meow",
+ "halloween",
+ "vagina",
+ "cat"
],
"moji": "🐱"
},
@@ -3711,13 +4069,13 @@
"pet",
"cat",
"kitten",
- "meow"
+ "halloween"
],
"moji": "🐈"
},
"cd": {
"unicode": "1F4BF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "optical disc",
"shortname": ":cd:",
"category": "objects",
@@ -3732,25 +4090,14 @@
"cd",
"computer",
"object",
- "office"
- ]
- },
- "celtic_cross": {
- "unicode": "1F548",
- "unicode_alternates": [],
- "name": "celtic cross",
- "shortname": ":celtic_cross:",
- "category": "objects_symbols",
- "aliases": [],
- "aliases_ascii": [],
- "keywords": [
- "religion",
- "symbol"
- ]
+ "office",
+ "electronics"
+ ],
+ "moji": "💿"
},
"chains": {
"unicode": "26D3",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "chains",
"shortname": ":chains:",
"category": "objects",
@@ -3758,32 +4105,55 @@
"aliases_ascii": [],
"keywords": [
"chain",
- "object"
- ]
+ "object",
+ "tool"
+ ],
+ "moji": "⛓"
},
"champagne": {
"unicode": "1F37E",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "bottle with popping cork",
"shortname": ":champagne:",
- "category": "foods",
+ "category": "food",
"aliases": [
":bottle_with_popping_cork:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "drink",
+ "cheers",
+ "alcohol",
+ "parties"
+ ],
+ "moji": "🍾"
+ },
+ "champagne_glass": {
+ "unicode": "1F942",
+ "unicode_alternates": [],
+ "name": "clinking glasses",
+ "shortname": ":champagne_glass:",
+ "category": "food",
+ "aliases": [
+ ":clinking_glass:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🥂"
},
"chart": {
"unicode": "1F4B9",
"unicode_alternates": [],
"name": "chart with upwards trend and yen sign",
"shortname": ":chart:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"graph",
- "green-square"
+ "green-square",
+ "symbol",
+ "money"
],
"moji": "💹"
},
@@ -3796,7 +4166,9 @@
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "graph"
+ "graph",
+ "work",
+ "office"
],
"moji": "📉"
},
@@ -3809,7 +4181,9 @@
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "graph"
+ "graph",
+ "work",
+ "office"
],
"moji": "📈"
},
@@ -3818,7 +4192,7 @@
"unicode_alternates": [],
"name": "chequered flag",
"shortname": ":checkered_flag:",
- "category": "objects",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -3832,28 +4206,32 @@
"flag",
"finish",
"complete",
- "end"
+ "end",
+ "object"
],
"moji": "🏁"
},
"cheese": {
"unicode": "1F9C0",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "cheese wedge",
"shortname": ":cheese:",
- "category": "foods",
+ "category": "food",
"aliases": [
":cheese_wedge:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "food"
+ ],
+ "moji": "🧀"
},
"cherries": {
"unicode": "1F352",
"unicode_alternates": [],
"name": "cherries",
"shortname": ":cherries:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -3862,7 +4240,6 @@
"cherry",
"cherries",
"tree",
- "fruit",
"pit"
],
"moji": "🍒"
@@ -3882,7 +4259,7 @@
"cherry",
"blossom",
"tree",
- "flower"
+ "tropical"
],
"moji": "🌸"
},
@@ -3899,8 +4276,9 @@
"squirrel",
"chestnut",
"roasted",
- "food",
- "tree"
+ "tree",
+ "nature",
+ "plant"
],
"moji": "🌰"
},
@@ -3927,7 +4305,7 @@
"unicode_alternates": [],
"name": "children crossing",
"shortname": ":children_crossing:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -3938,7 +4316,8 @@
"crossing",
"street",
"crosswalk",
- "slow"
+ "slow",
+ "symbol"
],
"moji": "🚸"
},
@@ -3952,15 +4331,17 @@
"aliases_ascii": [],
"keywords": [
"animal",
- "nature"
- ]
+ "nature",
+ "wildlife"
+ ],
+ "moji": "🐿"
},
"chocolate_bar": {
"unicode": "1F36B",
"unicode_alternates": [],
"name": "chocolate bar",
"shortname": ":chocolate_bar:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -3971,7 +4352,8 @@
"bar",
"candy",
"coca",
- "hershey&#039;s"
+ "hershey&#039;s",
+ "halloween"
],
"moji": "🍫"
},
@@ -3980,7 +4362,7 @@
"unicode_alternates": [],
"name": "christmas tree",
"shortname": ":christmas_tree:",
- "category": "objects",
+ "category": "nature",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -3990,17 +4372,17 @@
"vacation",
"xmas",
"christmas",
- "xmas",
"santa",
"holiday",
"winter",
- "december",
- "santa",
"evergreen",
"ornaments",
"jesus",
"gifts",
- "presents"
+ "presents",
+ "plant",
+ "holidays",
+ "trees"
],
"moji": "🎄"
},
@@ -4011,13 +4393,16 @@
],
"name": "church",
"shortname": ":church:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"building",
"christ",
- "religion"
+ "religion",
+ "places",
+ "wedding",
+ "condolence"
],
"moji": "⛪"
},
@@ -4026,7 +4411,7 @@
"unicode_alternates": [],
"name": "cinema",
"shortname": ":cinema:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -4035,10 +4420,11 @@
"movie",
"record",
"cinema",
- "movie",
"theater",
"motion",
- "picture"
+ "picture",
+ "symbol",
+ "camera"
],
"moji": "🎦"
},
@@ -4047,7 +4433,7 @@
"unicode_alternates": [],
"name": "circus tent",
"shortname": ":circus_tent:",
- "category": "places",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -4057,10 +4443,10 @@
"circus",
"tent",
"event",
- "carnival",
"big",
"top",
- "canvas"
+ "canvas",
+ "circus tent"
],
"moji": "🎪"
},
@@ -4069,7 +4455,7 @@
"unicode_alternates": [],
"name": "cityscape at dusk",
"shortname": ":city_dusk:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -4082,7 +4468,9 @@
"evening",
"metropolitan",
"night",
- "dark"
+ "dark",
+ "places",
+ "building"
],
"moji": "🌆"
},
@@ -4091,7 +4479,7 @@
"unicode_alternates": [],
"name": "sunset over buildings",
"shortname": ":city_sunset:",
- "category": "places",
+ "category": "travel",
"aliases": [
":city_sunrise:"
],
@@ -4106,7 +4494,11 @@
"morning",
"metropolitan",
"rise",
- "sun"
+ "sun",
+ "places",
+ "building",
+ "sky",
+ "vacation"
],
"moji": "🌇"
},
@@ -4115,7 +4507,7 @@
"unicode_alternates": [],
"name": "cityscape",
"shortname": ":cityscape:",
- "category": "travel_places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -4124,12 +4516,16 @@
"view",
"lights",
"buiildings",
- "metropolis"
- ]
+ "metropolis",
+ "places",
+ "building",
+ "vacation"
+ ],
+ "moji": "🏙"
},
"cl": {
"unicode": "1F191",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "squared cl",
"shortname": ":cl:",
"category": "symbols",
@@ -4143,14 +4539,15 @@
"clear",
"symbol",
"word"
- ]
+ ],
+ "moji": "🆑"
},
"clap": {
"unicode": "1F44F",
"unicode_alternates": [],
"name": "clapping hands sign",
"shortname": ":clap:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -4163,13 +4560,18 @@
"approval",
"sound",
"encouragement",
- "enthusiasm"
+ "enthusiasm",
+ "body",
+ "win",
+ "diversity",
+ "good",
+ "beautiful"
],
"moji": "👏"
},
"clap_tone1": {
"unicode": "1F44F-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "clapping hands sign tone 1",
"shortname": ":clap_tone1:",
"category": "people",
@@ -4185,11 +4587,12 @@
"sound",
"encouragement",
"enthusiasm"
- ]
+ ],
+ "moji": "👏🏻"
},
"clap_tone2": {
"unicode": "1F44F-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "clapping hands sign tone 2",
"shortname": ":clap_tone2:",
"category": "people",
@@ -4205,11 +4608,12 @@
"sound",
"encouragement",
"enthusiasm"
- ]
+ ],
+ "moji": "👏🏼"
},
"clap_tone3": {
"unicode": "1F44F-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "clapping hands sign tone 3",
"shortname": ":clap_tone3:",
"category": "people",
@@ -4225,11 +4629,12 @@
"sound",
"encouragement",
"enthusiasm"
- ]
+ ],
+ "moji": "👏🏽"
},
"clap_tone4": {
"unicode": "1F44F-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "clapping hands sign tone 4",
"shortname": ":clap_tone4:",
"category": "people",
@@ -4245,11 +4650,12 @@
"sound",
"encouragement",
"enthusiasm"
- ]
+ ],
+ "moji": "👏🏾"
},
"clap_tone5": {
"unicode": "1F44F-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "clapping hands sign tone 5",
"shortname": ":clap_tone5:",
"category": "people",
@@ -4265,14 +4671,15 @@
"sound",
"encouragement",
"enthusiasm"
- ]
+ ],
+ "moji": "👏🏿"
},
"clapper": {
"unicode": "1F3AC",
"unicode_alternates": [],
"name": "clapper board",
"shortname": ":clapper:",
- "category": "objects",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -4282,8 +4689,6 @@
"clapper",
"board",
"clapboard",
- "movie",
- "film",
"take"
],
"moji": "🎬"
@@ -4293,7 +4698,7 @@
"unicode_alternates": [],
"name": "classical building",
"shortname": ":classical_building:",
- "category": "travel_places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -4301,8 +4706,13 @@
"architecture",
"history",
"iconic",
- "genre"
- ]
+ "genre",
+ "places",
+ "building",
+ "travel",
+ "vacation"
+ ],
+ "moji": "🏛"
},
"clipboard": {
"unicode": "1F4CB",
@@ -4314,7 +4724,11 @@
"aliases_ascii": [],
"keywords": [
"documents",
- "stationery"
+ "stationery",
+ "object",
+ "work",
+ "office",
+ "write"
],
"moji": "📋"
},
@@ -4323,26 +4737,29 @@
"unicode_alternates": [],
"name": "mantlepiece clock",
"shortname": ":clock:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [
":mantlepiece_clock:"
],
"aliases_ascii": [],
"keywords": [
- "time"
- ]
+ "time",
+ "object"
+ ],
+ "moji": "🕰"
},
"clock1": {
"unicode": "1F550",
"unicode_alternates": [],
"name": "clock face one oclock",
"shortname": ":clock1:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"clock",
- "time"
+ "time",
+ "symbol"
],
"moji": "🕐"
},
@@ -4351,12 +4768,13 @@
"unicode_alternates": [],
"name": "clock face ten oclock",
"shortname": ":clock10:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"clock",
- "time"
+ "time",
+ "symbol"
],
"moji": "🕙"
},
@@ -4365,12 +4783,13 @@
"unicode_alternates": [],
"name": "clock face ten-thirty",
"shortname": ":clock1030:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"clock",
- "time"
+ "time",
+ "symbol"
],
"moji": "🕥"
},
@@ -4379,12 +4798,13 @@
"unicode_alternates": [],
"name": "clock face eleven oclock",
"shortname": ":clock11:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"clock",
- "time"
+ "time",
+ "symbol"
],
"moji": "🕚"
},
@@ -4393,12 +4813,13 @@
"unicode_alternates": [],
"name": "clock face eleven-thirty",
"shortname": ":clock1130:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"clock",
- "time"
+ "time",
+ "symbol"
],
"moji": "🕦"
},
@@ -4407,12 +4828,13 @@
"unicode_alternates": [],
"name": "clock face twelve oclock",
"shortname": ":clock12:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"clock",
- "time"
+ "time",
+ "symbol"
],
"moji": "🕛"
},
@@ -4421,25 +4843,28 @@
"unicode_alternates": [],
"name": "clock face twelve-thirty",
"shortname": ":clock1230:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"clock",
- "time"
- ]
+ "time",
+ "symbol"
+ ],
+ "moji": "🕧"
},
"clock130": {
"unicode": "1F55C",
"unicode_alternates": [],
"name": "clock face one-thirty",
"shortname": ":clock130:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"clock",
- "time"
+ "time",
+ "symbol"
],
"moji": "🕜"
},
@@ -4448,12 +4873,13 @@
"unicode_alternates": [],
"name": "clock face two oclock",
"shortname": ":clock2:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"clock",
- "time"
+ "time",
+ "symbol"
],
"moji": "🕑"
},
@@ -4462,12 +4888,13 @@
"unicode_alternates": [],
"name": "clock face two-thirty",
"shortname": ":clock230:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"clock",
- "time"
+ "time",
+ "symbol"
],
"moji": "🕝"
},
@@ -4476,12 +4903,13 @@
"unicode_alternates": [],
"name": "clock face three oclock",
"shortname": ":clock3:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"clock",
- "time"
+ "time",
+ "symbol"
],
"moji": "🕒"
},
@@ -4490,12 +4918,13 @@
"unicode_alternates": [],
"name": "clock face three-thirty",
"shortname": ":clock330:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"clock",
- "time"
+ "time",
+ "symbol"
],
"moji": "🕞"
},
@@ -4504,12 +4933,13 @@
"unicode_alternates": [],
"name": "clock face four oclock",
"shortname": ":clock4:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"clock",
- "time"
+ "time",
+ "symbol"
],
"moji": "🕓"
},
@@ -4518,12 +4948,13 @@
"unicode_alternates": [],
"name": "clock face four-thirty",
"shortname": ":clock430:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"clock",
- "time"
+ "time",
+ "symbol"
],
"moji": "🕟"
},
@@ -4532,12 +4963,13 @@
"unicode_alternates": [],
"name": "clock face five oclock",
"shortname": ":clock5:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"clock",
- "time"
+ "time",
+ "symbol"
],
"moji": "🕔"
},
@@ -4546,12 +4978,13 @@
"unicode_alternates": [],
"name": "clock face five-thirty",
"shortname": ":clock530:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"clock",
- "time"
+ "time",
+ "symbol"
],
"moji": "🕠"
},
@@ -4560,12 +4993,13 @@
"unicode_alternates": [],
"name": "clock face six oclock",
"shortname": ":clock6:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"clock",
- "time"
+ "time",
+ "symbol"
],
"moji": "🕕"
},
@@ -4574,12 +5008,13 @@
"unicode_alternates": [],
"name": "clock face six-thirty",
"shortname": ":clock630:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"clock",
- "time"
+ "time",
+ "symbol"
],
"moji": "🕡"
},
@@ -4588,12 +5023,13 @@
"unicode_alternates": [],
"name": "clock face seven oclock",
"shortname": ":clock7:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"clock",
- "time"
+ "time",
+ "symbol"
],
"moji": "🕖"
},
@@ -4602,12 +5038,13 @@
"unicode_alternates": [],
"name": "clock face seven-thirty",
"shortname": ":clock730:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"clock",
- "time"
+ "time",
+ "symbol"
],
"moji": "🕢"
},
@@ -4616,12 +5053,13 @@
"unicode_alternates": [],
"name": "clock face eight oclock",
"shortname": ":clock8:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"clock",
- "time"
+ "time",
+ "symbol"
],
"moji": "🕗"
},
@@ -4630,12 +5068,13 @@
"unicode_alternates": [],
"name": "clock face eight-thirty",
"shortname": ":clock830:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"clock",
- "time"
+ "time",
+ "symbol"
],
"moji": "🕣"
},
@@ -4644,12 +5083,13 @@
"unicode_alternates": [],
"name": "clock face nine oclock",
"shortname": ":clock9:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"clock",
- "time"
+ "time",
+ "symbol"
],
"moji": "🕘"
},
@@ -4658,29 +5098,16 @@
"unicode_alternates": [],
"name": "clock face nine-thirty",
"shortname": ":clock930:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"clock",
- "time"
+ "time",
+ "symbol"
],
"moji": "🕤"
},
- "clockwise_arrows": {
- "unicode": "1F5D8",
- "unicode_alternates": [],
- "name": "clockwise right and left semicircle arrows",
- "shortname": ":clockwise_arrows:",
- "category": "objects_symbols",
- "aliases": [
- ":clockwise_right_and_left_semicircle_arrows:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "sync"
- ]
- },
"closed_book": {
"unicode": "1F4D5",
"unicode_alternates": [],
@@ -4692,7 +5119,11 @@
"keywords": [
"knowledge",
"library",
- "read"
+ "read",
+ "object",
+ "office",
+ "write",
+ "book"
],
"moji": "📕"
},
@@ -4706,7 +5137,9 @@
"aliases_ascii": [],
"keywords": [
"privacy",
- "security"
+ "security",
+ "object",
+ "lock"
],
"moji": "🔐"
},
@@ -4715,7 +5148,7 @@
"unicode_alternates": [],
"name": "closed umbrella",
"shortname": ":closed_umbrella:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -4724,12 +5157,14 @@
"weather",
"umbrella",
"closed",
- "rain",
"moisture",
"protection",
"sun",
"ultraviolet",
- "uv"
+ "uv",
+ "object",
+ "sky",
+ "accessories"
],
"moji": "🌂"
},
@@ -4745,7 +5180,10 @@
"aliases_ascii": [],
"keywords": [
"sky",
- "weather"
+ "weather",
+ "cloud",
+ "cold",
+ "rain"
],
"moji": "☁"
},
@@ -4761,8 +5199,13 @@
"aliases_ascii": [],
"keywords": [
"weather",
- "thunder"
- ]
+ "thunder",
+ "sky",
+ "cloud",
+ "cold",
+ "rain"
+ ],
+ "moji": "🌩"
},
"cloud_rain": {
"unicode": "1F327",
@@ -4776,8 +5219,14 @@
"aliases_ascii": [],
"keywords": [
"weather",
- "wet"
- ]
+ "wet",
+ "winter",
+ "sky",
+ "cloud",
+ "cold",
+ "rain"
+ ],
+ "moji": "🌧"
},
"cloud_snow": {
"unicode": "1F328",
@@ -4791,8 +5240,13 @@
"aliases_ascii": [],
"keywords": [
"weather",
- "cold"
- ]
+ "cold",
+ "winter",
+ "sky",
+ "cloud",
+ "snow"
+ ],
+ "moji": "🌨"
},
"cloud_tornado": {
"unicode": "1F32A",
@@ -4807,8 +5261,24 @@
"keywords": [
"weather",
"destruction",
- "funnel"
- ]
+ "funnel",
+ "sky",
+ "cold"
+ ],
+ "moji": "🌪"
+ },
+ "clown": {
+ "unicode": "1F921",
+ "unicode_alternates": [],
+ "name": "clown face",
+ "shortname": ":clown:",
+ "category": "people",
+ "aliases": [
+ ":clown_face:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤡"
},
"clubs": {
"unicode": "2663",
@@ -4817,12 +5287,14 @@
],
"name": "black club suit",
"shortname": ":clubs:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"cards",
- "poker"
+ "poker",
+ "symbol",
+ "game"
],
"moji": "♣"
},
@@ -4831,7 +5303,7 @@
"unicode_alternates": [],
"name": "cocktail glass",
"shortname": ":cocktail:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -4841,11 +5313,11 @@
"drunk",
"cocktail",
"mixed",
- "drink",
- "alcohol",
"glass",
"martini",
- "bar"
+ "bar",
+ "girls night",
+ "parties"
],
"moji": "🍸"
},
@@ -4856,20 +5328,23 @@
],
"name": "hot beverage",
"shortname": ":coffee:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"beverage",
"cafe",
"drink",
- "espresso"
+ "espresso",
+ "caffeine",
+ "steam",
+ "morning"
],
"moji": "☕"
},
"coffin": {
"unicode": "26B0",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "coffin",
"shortname": ":coffin:",
"category": "objects",
@@ -4877,15 +5352,18 @@
"aliases_ascii": [],
"keywords": [
"death",
- "object"
- ]
+ "object",
+ "dead",
+ "rip"
+ ],
+ "moji": "⚰"
},
"cold_sweat": {
"unicode": "1F630",
"unicode_alternates": [],
"name": "face with open mouth and cold sweat",
"shortname": ":cold_sweat:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -4893,13 +5371,15 @@
"nervous",
"sweat",
"exasperated",
- "frustrated"
+ "frustrated",
+ "smiley",
+ "emotion"
],
"moji": "😰"
},
"comet": {
"unicode": "2604",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "comet",
"shortname": ":comet:",
"category": "nature",
@@ -4907,20 +5387,23 @@
"aliases_ascii": [],
"keywords": [
"object",
- "space"
- ]
+ "space",
+ "sky"
+ ],
+ "moji": "☄"
},
"compression": {
"unicode": "1F5DC",
"unicode_alternates": [],
"name": "compression",
"shortname": ":compression:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"reduce"
- ]
+ ],
+ "moji": "🗜"
},
"computer": {
"unicode": "1F4BB",
@@ -4932,25 +5415,13 @@
"aliases_ascii": [],
"keywords": [
"laptop",
- "tech"
+ "tech",
+ "electronics",
+ "work",
+ "office"
],
"moji": "💻"
},
- "computer_old": {
- "unicode": "1F5B3",
- "unicode_alternates": [],
- "name": "old personal computer",
- "shortname": ":computer_old:",
- "category": "objects_symbols",
- "aliases": [
- ":old_personal_computer:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "cpu",
- "terminal"
- ]
- },
"confetti_ball": {
"unicode": "1F38A",
"unicode_alternates": [],
@@ -4962,7 +5433,6 @@
"keywords": [
"festival",
"party",
- "party",
"congratulations",
"confetti",
"ball",
@@ -4970,7 +5440,13 @@
"win",
"birthday",
"new years",
- "wedding"
+ "wedding",
+ "object",
+ "holidays",
+ "cheers",
+ "girls night",
+ "boys night",
+ "parties"
],
"moji": "🎊"
},
@@ -4979,7 +5455,7 @@
"unicode_alternates": [],
"name": "confounded face",
"shortname": ":confounded:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -4991,7 +5467,11 @@
"amaze",
"perplex",
"puzzle",
- "mystify"
+ "mystify",
+ "sad",
+ "smiley",
+ "angry",
+ "emotion"
],
"moji": "😖"
},
@@ -5000,7 +5480,7 @@
"unicode_alternates": [],
"name": "confused face",
"shortname": ":confused:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [
">:\\",
@@ -5024,7 +5504,10 @@
"skeptical",
"undecided",
"uneasy",
- "hesitant"
+ "hesitant",
+ "smiley",
+ "surprised",
+ "emotion"
],
"moji": "😕"
},
@@ -5035,13 +5518,15 @@
],
"name": "circled ideograph congratulation",
"shortname": ":congratulations:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"chinese",
"japanese",
- "kanji"
+ "kanji",
+ "japan",
+ "symbol"
],
"moji": "㊗"
},
@@ -5050,19 +5535,20 @@
"unicode_alternates": [],
"name": "construction sign",
"shortname": ":construction:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"caution",
"progress",
- "wip"
+ "wip",
+ "object"
],
"moji": "🚧"
},
"construction_site": {
"unicode": "1F3D7",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "building construction",
"shortname": ":construction_site:",
"category": "travel",
@@ -5073,28 +5559,36 @@
"keywords": [
"site",
"work",
- "place"
- ]
+ "place",
+ "building",
+ "crane"
+ ],
+ "moji": "🏗"
},
"construction_worker": {
"unicode": "1F477",
"unicode_alternates": [],
"name": "construction worker",
"shortname": ":construction_worker:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"human",
"male",
"man",
- "wip"
+ "wip",
+ "people",
+ "hat",
+ "men",
+ "diversity",
+ "job"
],
"moji": "👷"
},
"construction_worker_tone1": {
"unicode": "1F477-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "construction worker tone 1",
"shortname": ":construction_worker_tone1:",
"category": "people",
@@ -5105,11 +5599,12 @@
"male",
"man",
"wip"
- ]
+ ],
+ "moji": "👷🏻"
},
"construction_worker_tone2": {
"unicode": "1F477-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "construction worker tone 2",
"shortname": ":construction_worker_tone2:",
"category": "people",
@@ -5120,11 +5615,12 @@
"male",
"man",
"wip"
- ]
+ ],
+ "moji": "👷🏼"
},
"construction_worker_tone3": {
"unicode": "1F477-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "construction worker tone 3",
"shortname": ":construction_worker_tone3:",
"category": "people",
@@ -5135,11 +5631,12 @@
"male",
"man",
"wip"
- ]
+ ],
+ "moji": "👷🏽"
},
"construction_worker_tone4": {
"unicode": "1F477-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "construction worker tone 4",
"shortname": ":construction_worker_tone4:",
"category": "people",
@@ -5150,11 +5647,12 @@
"male",
"man",
"wip"
- ]
+ ],
+ "moji": "👷🏾"
},
"construction_worker_tone5": {
"unicode": "1F477-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "construction worker tone 5",
"shortname": ":construction_worker_tone5:",
"category": "people",
@@ -5165,45 +5663,34 @@
"male",
"man",
"wip"
- ]
+ ],
+ "moji": "👷🏿"
},
"control_knobs": {
"unicode": "1F39B",
"unicode_alternates": [],
"name": "control knobs",
"shortname": ":control_knobs:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "dial"
- ]
- },
- "contruction_site": {
- "unicode": "1F3D7",
- "unicode_alternates": [],
- "name": "building construction",
- "shortname": ":contruction_site:",
- "category": "travel_places",
- "aliases": [
- ":building_construction:"
+ "dial",
+ "time"
],
- "aliases_ascii": [],
- "keywords": [
- "site",
- "work"
- ]
+ "moji": "🎛"
},
"convenience_store": {
"unicode": "1F3EA",
"unicode_alternates": [],
"name": "convenience store",
"shortname": ":convenience_store:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "building"
+ "building",
+ "places"
],
"moji": "🏪"
},
@@ -5212,7 +5699,7 @@
"unicode_alternates": [],
"name": "cookie",
"shortname": ":cookie:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -5224,21 +5711,44 @@
"dessert",
"biscuit",
"sweet",
- "chocolate"
+ "vagina"
],
"moji": "🍪"
},
+ "cooking": {
+ "unicode": "1F373",
+ "unicode_alternates": [],
+ "name": "cooking",
+ "shortname": ":cooking:",
+ "category": "food",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [
+ "breakfast",
+ "food",
+ "egg",
+ "fry",
+ "pan",
+ "flat",
+ "cook",
+ "frying",
+ "cooking",
+ "utensil"
+ ],
+ "moji": "🍳"
+ },
"cool": {
"unicode": "1F192",
"unicode_alternates": [],
"name": "squared cool",
"shortname": ":cool:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"blue-square",
- "words"
+ "words",
+ "symbol"
],
"moji": "🆒"
},
@@ -5247,7 +5757,7 @@
"unicode_alternates": [],
"name": "police officer",
"shortname": ":cop:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -5255,13 +5765,19 @@
"enforcement",
"law",
"man",
- "police"
+ "police",
+ "people",
+ "hat",
+ "men",
+ "diversity",
+ "job",
+ "911"
],
"moji": "👮"
},
"cop_tone1": {
"unicode": "1F46E-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "police officer tone 1",
"shortname": ":cop_tone1:",
"category": "people",
@@ -5273,11 +5789,12 @@
"law",
"man",
"cop"
- ]
+ ],
+ "moji": "👮🏻"
},
"cop_tone2": {
"unicode": "1F46E-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "police officer tone 2",
"shortname": ":cop_tone2:",
"category": "people",
@@ -5289,11 +5806,12 @@
"law",
"man",
"cop"
- ]
+ ],
+ "moji": "👮🏼"
},
"cop_tone3": {
"unicode": "1F46E-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "police officer tone 3",
"shortname": ":cop_tone3:",
"category": "people",
@@ -5305,11 +5823,12 @@
"law",
"man",
"cop"
- ]
+ ],
+ "moji": "👮🏽"
},
"cop_tone4": {
"unicode": "1F46E-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "police officer tone 4",
"shortname": ":cop_tone4:",
"category": "people",
@@ -5321,11 +5840,12 @@
"law",
"man",
"cop"
- ]
+ ],
+ "moji": "👮🏾"
},
"cop_tone5": {
"unicode": "1F46E-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "police officer tone 5",
"shortname": ":cop_tone5:",
"category": "people",
@@ -5337,7 +5857,8 @@
"law",
"man",
"cop"
- ]
+ ],
+ "moji": "👮🏿"
},
"copyright": {
"moji": "©",
@@ -5345,12 +5866,13 @@
"unicode_alternates": [],
"name": "copyright sign",
"shortname": ":copyright:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"ip",
- "license"
+ "license",
+ "symbol"
]
},
"corn": {
@@ -5358,7 +5880,7 @@
"unicode_alternates": [],
"name": "ear of maize",
"shortname": ":corn:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -5367,7 +5889,6 @@
"vegetable",
"corn",
"maize",
- "food",
"iowa",
"kernel",
"popcorn",
@@ -5375,7 +5896,8 @@
"yellow",
"stalk",
"cob",
- "ear"
+ "ear",
+ "vegetables"
],
"moji": "🌽"
},
@@ -5384,7 +5906,7 @@
"unicode_alternates": [],
"name": "couch and lamp",
"shortname": ":couch:",
- "category": "travel_places",
+ "category": "objects",
"aliases": [
":couch_and_lamp:"
],
@@ -5397,15 +5919,17 @@
"leather",
"microfiber",
"sit",
- "relax"
- ]
+ "relax",
+ "object"
+ ],
+ "moji": "🛋"
},
"couple": {
"unicode": "1F46B",
"unicode_alternates": [],
"name": "man and woman holding hands",
"shortname": ":couple:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -5417,7 +5941,9 @@
"love",
"marriage",
"people",
- "valentines"
+ "valentines",
+ "sex",
+ "creationism"
],
"moji": "👫"
},
@@ -5440,15 +5966,21 @@
"like",
"love",
"marriage",
- "valentines"
- ]
+ "valentines",
+ "people",
+ "gay",
+ "men",
+ "sex",
+ "lgbt"
+ ],
+ "moji": "👨‍❤️‍👨"
},
"couple_with_heart": {
"unicode": "1F491",
"unicode_alternates": [],
"name": "couple with heart",
"shortname": ":couple_with_heart:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -5458,7 +5990,9 @@
"like",
"love",
"marriage",
- "valentines"
+ "valentines",
+ "people",
+ "sex"
],
"moji": "💑"
},
@@ -5481,15 +6015,20 @@
"like",
"love",
"marriage",
- "valentines"
- ]
+ "valentines",
+ "people",
+ "women",
+ "sex",
+ "lgbt"
+ ],
+ "moji": "👩‍❤️‍👩"
},
"couplekiss": {
"unicode": "1F48F",
"unicode_alternates": [],
"name": "kiss",
"shortname": ":couplekiss:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -5497,7 +6036,9 @@
"like",
"love",
"marriage",
- "valentines"
+ "valentines",
+ "people",
+ "sex"
],
"moji": "💏"
},
@@ -5532,28 +6073,44 @@
"cow",
"milk",
"dairy",
- "beef",
"bessie",
"moo"
],
"moji": "🐄"
},
+ "cowboy": {
+ "unicode": "1F920",
+ "unicode_alternates": [],
+ "name": "face with cowboy hat",
+ "shortname": ":cowboy:",
+ "category": "people",
+ "aliases": [
+ ":face_with_cowboy_hat:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤠"
+ },
"crab": {
"unicode": "1F980",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "crab",
"shortname": ":crab:",
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "tropical",
+ "animal"
+ ],
+ "moji": "🦀"
},
"crayon": {
"unicode": "1F58D",
"unicode_alternates": [],
"name": "lower left crayon",
"shortname": ":crayon:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [
":lower_left_crayon:"
],
@@ -5562,8 +6119,11 @@
"write",
"draw",
"color",
- "wax"
- ]
+ "wax",
+ "object",
+ "office"
+ ],
+ "moji": "🖍"
},
"credit_card": {
"unicode": "1F4B3",
@@ -5588,7 +6148,9 @@
"visa",
"american express",
"wallet",
- "signature"
+ "signature",
+ "object",
+ "boys night"
],
"moji": "💳"
},
@@ -5606,15 +6168,16 @@
"crescent",
"waxing",
"sky",
- "night",
"cheese",
- "phase"
+ "phase",
+ "space",
+ "goodnight"
],
"moji": "🌙"
},
"cricket": {
"unicode": "1F3CF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "cricket bat and ball",
"shortname": ":cricket:",
"category": "activity",
@@ -5622,7 +6185,12 @@
":cricket_bat_ball:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "ball",
+ "sport",
+ "cricket"
+ ],
+ "moji": "🏏"
},
"crocodile": {
"unicode": "1F40A",
@@ -5639,13 +6207,26 @@
"croc",
"alligator",
"gator",
- "cranky"
+ "cranky",
+ "wildlife",
+ "reptile"
],
"moji": "🐊"
},
+ "croissant": {
+ "unicode": "1F950",
+ "unicode_alternates": [],
+ "name": "croissant",
+ "shortname": ":croissant:",
+ "category": "food",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🥐"
+ },
"cross": {
"unicode": "271D",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "latin cross",
"shortname": ":cross:",
"category": "symbols",
@@ -5657,53 +6238,8 @@
"religion",
"symbol",
"christian"
- ]
- },
- "cross_heavy": {
- "unicode": "1F547",
- "unicode_alternates": [],
- "name": "heavy latin cross",
- "shortname": ":cross_heavy:",
- "category": "objects_symbols",
- "aliases": [
- ":heavy_latin_cross:"
],
- "aliases_ascii": [],
- "keywords": [
- "religion",
- "symbol"
- ]
- },
- "cross_white": {
- "unicode": "1F546",
- "unicode_alternates": [],
- "name": "white latin cross",
- "shortname": ":cross_white:",
- "category": "objects_symbols",
- "aliases": [
- ":white_latin_cross:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "religion",
- "symbol"
- ]
- },
- "crossbones": {
- "unicode": "1F571",
- "unicode_alternates": [],
- "name": "black skull and crossbones",
- "shortname": ":crossbones:",
- "category": "objects_symbols",
- "aliases": [
- ":black_skull_and_crossbones:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "poison",
- "danger",
- "death"
- ]
+ "moji": "✝"
},
"crossed_flags": {
"unicode": "1F38C",
@@ -5714,13 +6250,14 @@
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "japan"
+ "japan",
+ "object"
],
"moji": "🎌"
},
"crossed_swords": {
"unicode": "2694",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "crossed swords",
"shortname": ":crossed_swords:",
"category": "objects",
@@ -5729,21 +6266,25 @@
"keywords": [
"object",
"weapon"
- ]
+ ],
+ "moji": "⚔"
},
"crown": {
"unicode": "1F451",
"unicode_alternates": [],
"name": "crown",
"shortname": ":crown:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"king",
"kod",
"leader",
- "royalty"
+ "royalty",
+ "object",
+ "gem",
+ "accessories"
],
"moji": "👑"
},
@@ -5752,7 +6293,7 @@
"unicode_alternates": [],
"name": "passenger ship",
"shortname": ":cruise_ship:",
- "category": "travel_places",
+ "category": "travel",
"aliases": [
":passenger_ship:"
],
@@ -5760,15 +6301,18 @@
"keywords": [
"titanic",
"transportation",
- "boat"
- ]
+ "boat",
+ "travel",
+ "vacation"
+ ],
+ "moji": "🛳"
},
"cry": {
"unicode": "1F622",
"unicode_alternates": [],
"name": "crying face",
"shortname": ":cry:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [
":'(",
@@ -5779,11 +6323,14 @@
"keywords": [
"face",
"sad",
- "sad",
"cry",
"tear",
"weep",
- "tears"
+ "tears",
+ "smiley",
+ "emotion",
+ "rip",
+ "heartbreak"
],
"moji": "😢"
},
@@ -5792,7 +6339,7 @@
"unicode_alternates": [],
"name": "crying cat face",
"shortname": ":crying_cat_face:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -5804,8 +6351,6 @@
"cry",
"cat",
"sob",
- "tears",
- "sad",
"melancholy",
"morn",
"somber",
@@ -5823,16 +6368,29 @@
"aliases_ascii": [],
"keywords": [
"disco",
- "party"
+ "party",
+ "object",
+ "ball"
],
"moji": "🔮"
},
+ "cucumber": {
+ "unicode": "1F952",
+ "unicode_alternates": [],
+ "name": "cucumber",
+ "shortname": ":cucumber:",
+ "category": "food",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🥒"
+ },
"cupid": {
"unicode": "1F498",
"unicode_alternates": [],
"name": "heart with arrow",
"shortname": ":cupid:",
- "category": "emoticons",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -5840,7 +6398,8 @@
"heart",
"like",
"love",
- "valentines"
+ "valentines",
+ "symbol"
],
"moji": "💘"
},
@@ -5849,11 +6408,12 @@
"unicode_alternates": [],
"name": "curly loop",
"shortname": ":curly_loop:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "scribble"
+ "scribble",
+ "symbol"
],
"moji": "➰"
},
@@ -5862,13 +6422,14 @@
"unicode_alternates": [],
"name": "currency exchange",
"shortname": ":currency_exchange:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"dollar",
"money",
- "travel"
+ "travel",
+ "symbol"
],
"moji": "💱"
},
@@ -5877,7 +6438,7 @@
"unicode_alternates": [],
"name": "curry and rice",
"shortname": ":curry:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -5888,7 +6449,6 @@
"curry",
"spice",
"flavor",
- "food",
"meal"
],
"moji": "🍛"
@@ -5898,7 +6458,7 @@
"unicode_alternates": [],
"name": "custard",
"shortname": ":custard:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -5920,7 +6480,7 @@
"unicode_alternates": [],
"name": "customs",
"shortname": ":customs:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -5932,7 +6492,8 @@
"goods",
"check",
"authority",
- "government"
+ "government",
+ "symbol"
],
"moji": "🛃"
},
@@ -5942,7 +6503,7 @@
"unicode_alternates": [],
"name": "cyclone",
"shortname": ":cyclone:",
- "category": "nature",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -5954,7 +6515,9 @@
"hurricane",
"typhoon",
"storm",
- "ocean"
+ "ocean",
+ "symbol",
+ "drugs"
]
},
"dagger": {
@@ -5962,22 +6525,25 @@
"unicode_alternates": [],
"name": "dagger knife",
"shortname": ":dagger:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [
":dagger_knife:"
],
"aliases_ascii": [],
"keywords": [
"blade",
- "knife"
- ]
+ "knife",
+ "object",
+ "weapon"
+ ],
+ "moji": "🗡"
},
"dancer": {
"unicode": "1F483",
"unicode_alternates": [],
"name": "dancer",
"shortname": ":dancer:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -5995,13 +6561,18 @@
"ballet",
"tango",
"cha cha",
- "music"
+ "music",
+ "people",
+ "women",
+ "sexy",
+ "diversity",
+ "girls night"
],
"moji": "💃"
},
"dancer_tone1": {
"unicode": "1F483-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "dancer tone 1",
"shortname": ":dancer_tone1:",
"category": "people",
@@ -6021,11 +6592,12 @@
"tango",
"cha cha",
"music"
- ]
+ ],
+ "moji": "💃🏻"
},
"dancer_tone2": {
"unicode": "1F483-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "dancer tone 2",
"shortname": ":dancer_tone2:",
"category": "people",
@@ -6045,11 +6617,12 @@
"tango",
"cha cha",
"music"
- ]
+ ],
+ "moji": "💃🏼"
},
"dancer_tone3": {
"unicode": "1F483-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "dancer tone 3",
"shortname": ":dancer_tone3:",
"category": "people",
@@ -6069,11 +6642,12 @@
"tango",
"cha cha",
"music"
- ]
+ ],
+ "moji": "💃🏽"
},
"dancer_tone4": {
"unicode": "1F483-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "dancer tone 4",
"shortname": ":dancer_tone4:",
"category": "people",
@@ -6093,11 +6667,12 @@
"tango",
"cha cha",
"music"
- ]
+ ],
+ "moji": "💃🏾"
},
"dancer_tone5": {
"unicode": "1F483-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "dancer tone 5",
"shortname": ":dancer_tone5:",
"category": "people",
@@ -6117,14 +6692,15 @@
"tango",
"cha cha",
"music"
- ]
+ ],
+ "moji": "💃🏿"
},
"dancers": {
"unicode": "1F46F",
"unicode_alternates": [],
"name": "woman with bunny ears",
"shortname": ":dancers:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -6137,8 +6713,13 @@
"showgirl",
"playboy",
"costume",
- "bunny",
- "cancan"
+ "cancan",
+ "people",
+ "sexy",
+ "girls night",
+ "boys night",
+ "parties",
+ "dance"
],
"moji": "👯"
},
@@ -6147,7 +6728,7 @@
"unicode_alternates": [],
"name": "dango",
"shortname": ":dango:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -6166,20 +6747,24 @@
"unicode_alternates": [],
"name": "dark sunglasses",
"shortname": ":dark_sunglasses:",
- "category": "objects_symbols",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"shades",
- "eyes"
- ]
+ "eyes",
+ "fashion",
+ "glasses",
+ "accessories"
+ ],
+ "moji": "🕶"
},
"dart": {
"unicode": "1F3AF",
"unicode_alternates": [],
"name": "direct hit",
"shortname": ":dart:",
- "category": "objects",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -6190,10 +6775,10 @@
"bullseye",
"dart",
"archery",
- "game",
"fletching",
"arrow",
- "sport"
+ "sport",
+ "boys night"
],
"moji": "🎯"
},
@@ -6202,14 +6787,17 @@
"unicode_alternates": [],
"name": "dash symbol",
"shortname": ":dash:",
- "category": "emoticons",
+ "category": "nature",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"air",
"fast",
"shoo",
- "wind"
+ "wind",
+ "cloud",
+ "cold",
+ "smoking"
],
"moji": "💨"
},
@@ -6223,7 +6811,9 @@
"aliases_ascii": [],
"keywords": [
"calendar",
- "schedule"
+ "schedule",
+ "object",
+ "office"
],
"moji": "📅"
},
@@ -6242,16 +6832,29 @@
"tree",
"leaves",
"fall",
- "color"
+ "color",
+ "camp",
+ "trees"
],
"moji": "🌳"
},
+ "deer": {
+ "unicode": "1F98C",
+ "unicode_alternates": [],
+ "name": "deer",
+ "shortname": ":deer:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🦌"
+ },
"department_store": {
"unicode": "1F3EC",
"unicode_alternates": [],
"name": "department store",
"shortname": ":department_store:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -6262,31 +6865,17 @@
"store",
"retail",
"sale",
- "merchandise"
+ "merchandise",
+ "places"
],
"moji": "🏬"
},
- "descending_notes": {
- "unicode": "1F39D",
- "unicode_alternates": [],
- "name": "beamed descending musical notes",
- "shortname": ":descending_notes:",
- "category": "objects_symbols",
- "aliases": [],
- "aliases_ascii": [],
- "keywords": [
- "score",
- "music",
- "sound",
- "tone"
- ]
- },
"desert": {
"unicode": "1F3DC",
"unicode_alternates": [],
"name": "desert",
"shortname": ":desert:",
- "category": "travel_places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -6295,41 +6884,36 @@
"sandy",
"cactus",
"sunny",
- "barren"
- ]
+ "barren",
+ "places",
+ "travel",
+ "vacation"
+ ],
+ "moji": "🏜"
},
"desktop": {
"unicode": "1F5A5",
"unicode_alternates": [],
"name": "desktop computer",
"shortname": ":desktop:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [
":desktop_computer:"
],
"aliases_ascii": [],
"keywords": [
- "cpu"
- ]
- },
- "desktop_window": {
- "unicode": "1F5D4",
- "unicode_alternates": [],
- "name": "desktop window",
- "shortname": ":desktop_window:",
- "category": "objects_symbols",
- "aliases": [],
- "aliases_ascii": [],
- "keywords": [
- "computer"
- ]
+ "cpu",
+ "electronics",
+ "work"
+ ],
+ "moji": "🖥"
},
"diamond_shape_with_a_dot_inside": {
"unicode": "1F4A0",
"unicode_alternates": [],
"name": "diamond shape with a dot inside",
"shortname": ":diamond_shape_with_a_dot_inside:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -6339,7 +6923,8 @@
"kawaii",
"japanese",
"glyph",
- "adorable"
+ "adorable",
+ "symbol"
],
"moji": "💠"
},
@@ -6350,12 +6935,15 @@
],
"name": "black diamond suit",
"shortname": ":diamonds:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"cards",
- "poker"
+ "poker",
+ "shapes",
+ "symbol",
+ "game"
],
"moji": "♦"
},
@@ -6364,7 +6952,7 @@
"unicode_alternates": [],
"name": "disappointed face",
"shortname": ":disappointed:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [
">:[",
@@ -6382,7 +6970,10 @@
"discouraged",
"face",
"sad",
- "upset"
+ "upset",
+ "smiley",
+ "tired",
+ "emotion"
],
"moji": "😞"
},
@@ -6391,7 +6982,7 @@
"unicode_alternates": [],
"name": "disappointed but relieved face",
"shortname": ":disappointed_relieved:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -6400,7 +6991,12 @@
"phew",
"sweat",
"disappoint",
- "relief"
+ "relief",
+ "sad",
+ "smiley",
+ "stressed",
+ "cry",
+ "emotion"
],
"moji": "😥"
},
@@ -6409,22 +7005,25 @@
"unicode_alternates": [],
"name": "card index dividers",
"shortname": ":dividers:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [
":card_index_dividers:"
],
"aliases_ascii": [],
"keywords": [
"stationery",
- "rolodex"
- ]
+ "rolodex",
+ "work",
+ "office"
+ ],
+ "moji": "🗂"
},
"dizzy": {
"unicode": "1F4AB",
"unicode_alternates": [],
"name": "dizzy symbol",
"shortname": ":dizzy:",
- "category": "emoticons",
+ "category": "nature",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -6437,7 +7036,7 @@
"intoxicated",
"squeans",
"starburst",
- "star"
+ "symbol"
],
"moji": "💫"
},
@@ -6446,7 +7045,7 @@
"unicode_alternates": [],
"name": "dizzy face",
"shortname": ":dizzy_face:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [
"#-)",
@@ -6463,7 +7062,13 @@
"face",
"spent",
"unconscious",
- "xox"
+ "xox",
+ "smiley",
+ "surprised",
+ "dead",
+ "wow",
+ "emotion",
+ "omg"
],
"moji": "😵"
},
@@ -6472,7 +7077,7 @@
"unicode_alternates": [],
"name": "do not litter symbol",
"shortname": ":do_not_litter:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -6480,40 +7085,13 @@
"garbage",
"trash",
"litter",
- "garbage",
"waste",
"no",
"can",
- "trash"
+ "symbol"
],
"moji": "🚯"
},
- "document": {
- "unicode": "1F5CE",
- "unicode_alternates": [],
- "name": "document",
- "shortname": ":document:",
- "category": "objects_symbols",
- "aliases": [],
- "aliases_ascii": [],
- "keywords": [
- "page"
- ]
- },
- "document_text": {
- "unicode": "1F5B9",
- "unicode_alternates": [],
- "name": "document with text",
- "shortname": ":document_text:",
- "category": "objects_symbols",
- "aliases": [
- ":document_with_text:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "page"
- ]
- },
"dog": {
"unicode": "1F436",
"unicode_alternates": [],
@@ -6526,7 +7104,9 @@
"animal",
"friend",
"nature",
- "woof"
+ "woof",
+ "dog",
+ "pug"
],
"moji": "🐶"
},
@@ -6546,11 +7126,10 @@
"pet",
"dog",
"puppy",
- "pet",
- "friend",
"woof",
"bark",
- "fido"
+ "fido",
+ "pug"
],
"moji": "🐕"
},
@@ -6571,8 +7150,6 @@
"canada",
"australia",
"banknote",
- "money",
- "currency",
"paper",
"cash",
"bills"
@@ -6593,7 +7170,6 @@
"toy",
"dolls",
"japan",
- "japanese",
"day",
"girls",
"emperor",
@@ -6602,7 +7178,8 @@
"blessing",
"imperial",
"family",
- "royal"
+ "royal",
+ "people"
],
"moji": "🎎"
},
@@ -6621,7 +7198,9 @@
"flipper",
"nature",
"ocean",
- "sea"
+ "sea",
+ "wildlife",
+ "tropical"
],
"moji": "🐬"
},
@@ -6641,8 +7220,7 @@
"doorway",
"entrance",
"enter",
- "exit",
- "entry"
+ "object"
],
"moji": "🚪"
},
@@ -6651,7 +7229,7 @@
"unicode_alternates": [],
"name": "doughnut",
"shortname": ":doughnut:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -6666,8 +7244,7 @@
"dessert",
"breakfast",
"police",
- "homer",
- "sweet"
+ "homer"
],
"moji": "🍩"
},
@@ -6676,15 +7253,17 @@
"unicode_alternates": [],
"name": "dove of peace",
"shortname": ":dove:",
- "category": "objects_symbols",
+ "category": "nature",
"aliases": [
":dove_of_peace:"
],
"aliases_ascii": [],
"keywords": [
"symbol",
- "bird"
- ]
+ "bird",
+ "animal"
+ ],
+ "moji": "🕊"
},
"dragon": {
"unicode": "1F409",
@@ -6703,7 +7282,8 @@
"dragon",
"fire",
"legendary",
- "myth"
+ "roar",
+ "reptile"
],
"moji": "🐉"
},
@@ -6725,7 +7305,9 @@
"head",
"fire",
"legendary",
- "myth"
+ "roar",
+ "monster",
+ "reptile"
],
"moji": "🐲"
},
@@ -6734,12 +7316,15 @@
"unicode_alternates": [],
"name": "dress",
"shortname": ":dress:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"clothes",
- "fashion"
+ "fashion",
+ "women",
+ "sexy",
+ "girls night"
],
"moji": "👗"
},
@@ -6758,23 +7343,35 @@
"dromedary",
"camel",
"hump",
- "desert",
"middle east",
"heat",
- "hot",
"water",
"hump day",
"wednesday",
- "sex"
+ "sex",
+ "wildlife"
],
"moji": "🐪"
},
+ "drooling_face": {
+ "unicode": "1F924",
+ "unicode_alternates": [],
+ "name": "drooling face",
+ "shortname": ":drooling_face:",
+ "category": "people",
+ "aliases": [
+ ":drool:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤤"
+ },
"droplet": {
"unicode": "1F4A7",
"unicode_alternates": [],
"name": "droplet",
"shortname": ":droplet:",
- "category": "emoticons",
+ "category": "nature",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -6784,7 +7381,6 @@
"drop",
"droplet",
"h20",
- "water",
"aqua",
"tear",
"sweat",
@@ -6792,10 +7388,36 @@
"moisture",
"wet",
"moist",
- "spit"
+ "spit",
+ "weather",
+ "sky"
],
"moji": "💧"
},
+ "drum": {
+ "unicode": "1F941",
+ "unicode_alternates": [],
+ "name": "drum with drumsticks",
+ "shortname": ":drum:",
+ "category": "activity",
+ "aliases": [
+ ":drum_with_drumsticks:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🥁"
+ },
+ "duck": {
+ "unicode": "1F986",
+ "unicode_alternates": [],
+ "name": "duck",
+ "shortname": ":duck:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🦆"
+ },
"dvd": {
"unicode": "1F4C0",
"unicode_alternates": [],
@@ -6807,7 +7429,8 @@
"keywords": [
"cd",
"disc",
- "disk"
+ "disk",
+ "electronics"
],
"moji": "📀"
},
@@ -6823,23 +7446,37 @@
"aliases_ascii": [],
"keywords": [
"communication",
- "inbox"
+ "inbox",
+ "office"
],
"moji": "📧"
},
+ "eagle": {
+ "unicode": "1F985",
+ "unicode_alternates": [],
+ "name": "eagle",
+ "shortname": ":eagle:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🦅"
+ },
"ear": {
"unicode": "1F442",
"unicode_alternates": [],
"name": "ear",
"shortname": ":ear:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"face",
"hear",
"listen",
- "sound"
+ "sound",
+ "body",
+ "diversity"
],
"moji": "👂"
},
@@ -6857,14 +7494,14 @@
"ear",
"rice",
"food",
- "plant",
- "seed"
+ "seed",
+ "leaf"
],
"moji": "🌾"
},
"ear_tone1": {
"unicode": "1F442-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "ear tone 1",
"shortname": ":ear_tone1:",
"category": "people",
@@ -6874,11 +7511,12 @@
"hear",
"listen",
"sound"
- ]
+ ],
+ "moji": "👂🏻"
},
"ear_tone2": {
"unicode": "1F442-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "ear tone 2",
"shortname": ":ear_tone2:",
"category": "people",
@@ -6888,11 +7526,12 @@
"hear",
"listen",
"sound"
- ]
+ ],
+ "moji": "👂🏼"
},
"ear_tone3": {
"unicode": "1F442-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "ear tone 3",
"shortname": ":ear_tone3:",
"category": "people",
@@ -6902,11 +7541,12 @@
"hear",
"listen",
"sound"
- ]
+ ],
+ "moji": "👂🏽"
},
"ear_tone4": {
"unicode": "1F442-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "ear tone 4",
"shortname": ":ear_tone4:",
"category": "people",
@@ -6916,11 +7556,12 @@
"hear",
"listen",
"sound"
- ]
+ ],
+ "moji": "👂🏾"
},
"ear_tone5": {
"unicode": "1F442-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "ear tone 5",
"shortname": ":ear_tone5:",
"category": "people",
@@ -6930,7 +7571,8 @@
"hear",
"listen",
"sound"
- ]
+ ],
+ "moji": "👂🏿"
},
"earth_africa": {
"unicode": "1F30D",
@@ -6945,12 +7587,13 @@
"international",
"world",
"earth",
- "globe",
"space",
"planet",
"africa",
"europe",
- "home"
+ "home",
+ "map",
+ "vacation"
],
"moji": "🌍"
},
@@ -6968,14 +7611,15 @@
"international",
"world",
"earth",
- "globe",
"space",
"planet",
"north",
"south",
"america",
"americas",
- "home"
+ "home",
+ "map",
+ "vacation"
],
"moji": "🌎"
},
@@ -6993,43 +7637,33 @@
"international",
"world",
"earth",
- "globe",
"space",
"planet",
"asia",
"australia",
- "home"
+ "home",
+ "map",
+ "vacation"
],
"moji": "🌏"
},
"egg": {
- "unicode": "1F373",
+ "unicode": "1F95A",
"unicode_alternates": [],
- "name": "cooking",
+ "name": "egg",
"shortname": ":egg:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
- "keywords": [
- "breakfast",
- "food",
- "egg",
- "fry",
- "pan",
- "flat",
- "cook",
- "frying",
- "cooking",
- "utensil"
- ],
- "moji": "🍳"
+ "keywords": [],
+ "moji": "🥚"
},
"eggplant": {
"unicode": "1F346",
"unicode_alternates": [],
"name": "aubergine",
"shortname": ":eggplant:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -7038,10 +7672,10 @@
"nature",
"vegetable",
"eggplant",
- "aubergine",
"fruit",
"purple",
- "penis"
+ "penis",
+ "vegetables"
],
"moji": "🍆"
},
@@ -7051,15 +7685,18 @@
"unicode_alternates": [
"0038-FE0F-20E3"
],
- "name": "digit eight",
+ "name": "keycap digit eight",
"shortname": ":eight:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"8",
"blue-square",
- "numbers"
+ "numbers",
+ "number",
+ "math",
+ "symbol"
]
},
"eight_pointed_black_star": {
@@ -7069,10 +7706,12 @@
],
"name": "eight pointed black star",
"shortname": ":eight_pointed_black_star:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": [],
+ "keywords": [
+ "symbol"
+ ],
"moji": "✴"
},
"eight_spoked_asterisk": {
@@ -7082,16 +7721,32 @@
],
"name": "eight spoked asterisk",
"shortname": ":eight_spoked_asterisk:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"green-square",
"sparkle",
- "star"
+ "star",
+ "symbol"
],
"moji": "✳"
},
+ "eject": {
+ "unicode": "23CF",
+ "unicode_alternates": [
+ "23CF-FE0F"
+ ],
+ "name": "eject symbol",
+ "shortname": ":eject:",
+ "category": "symbols",
+ "aliases": [
+ ":eject_symbol:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "⏏"
+ },
"electric_plug": {
"unicode": "1F50C",
"unicode_alternates": [],
@@ -7102,7 +7757,8 @@
"aliases_ascii": [],
"keywords": [
"charger",
- "power"
+ "power",
+ "electronics"
],
"moji": "🔌"
},
@@ -7118,7 +7774,8 @@
"animal",
"nature",
"nose",
- "thailand"
+ "thailand",
+ "wildlife"
],
"moji": "🐘"
},
@@ -7127,12 +7784,13 @@
"unicode_alternates": [],
"name": "end with leftwards arrow above",
"shortname": ":end:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"arrow",
- "words"
+ "words",
+ "symbol"
],
"moji": "🔚"
},
@@ -7150,78 +7808,13 @@
"communication",
"letter",
"mail",
- "postal"
+ "postal",
+ "object",
+ "office",
+ "write"
],
"moji": "✉"
},
- "envelope_back": {
- "unicode": "1F582",
- "unicode_alternates": [],
- "name": "back of envelope",
- "shortname": ":envelope_back:",
- "category": "objects_symbols",
- "aliases": [
- ":back_of_envelope:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "communication",
- "letter",
- "mail",
- "postal"
- ]
- },
- "envelope_flying": {
- "unicode": "1F585",
- "unicode_alternates": [],
- "name": "flying envelope",
- "shortname": ":envelope_flying:",
- "category": "objects_symbols",
- "aliases": [
- ":flying_envelope:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "communication",
- "letter",
- "mail",
- "postal"
- ]
- },
- "envelope_stamped": {
- "unicode": "1F583",
- "unicode_alternates": [],
- "name": "stamped envelope",
- "shortname": ":envelope_stamped:",
- "category": "objects_symbols",
- "aliases": [
- ":stamped_envelope:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "communication",
- "letter",
- "mail",
- "postal"
- ]
- },
- "envelope_stamped_pen": {
- "unicode": "1F586",
- "unicode_alternates": [],
- "name": "pen over stamped envelope",
- "shortname": ":envelope_stamped_pen:",
- "category": "objects_symbols",
- "aliases": [
- ":pen_over_stamped_envelope:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "communication",
- "letter",
- "mail",
- "postal"
- ]
- },
"envelope_with_arrow": {
"unicode": "1F4E9",
"unicode_alternates": [],
@@ -7231,7 +7824,9 @@
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "email"
+ "email",
+ "object",
+ "office"
],
"moji": "📩"
},
@@ -7250,8 +7845,6 @@
"euro",
"europe",
"banknote",
- "money",
- "currency",
"paper",
"cash",
"bills"
@@ -7263,7 +7856,7 @@
"unicode_alternates": [],
"name": "european castle",
"shortname": ":european_castle:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -7273,7 +7866,6 @@
"castle",
"european",
"residence",
- "royalty",
"disneyland",
"disney",
"fort",
@@ -7287,7 +7879,10 @@
"queen",
"fortress",
"nobel",
- "stronghold"
+ "stronghold",
+ "places",
+ "travel",
+ "vacation"
],
"moji": "🏰"
},
@@ -7296,11 +7891,13 @@
"unicode_alternates": [],
"name": "european post office",
"shortname": ":european_post_office:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "building"
+ "building",
+ "places",
+ "post office"
],
"moji": "🏤"
},
@@ -7318,7 +7915,10 @@
"evergreen",
"tree",
"needles",
- "christmas"
+ "christmas",
+ "holidays",
+ "camp",
+ "trees"
],
"moji": "🌲"
},
@@ -7329,11 +7929,13 @@
],
"name": "heavy exclamation mark symbol",
"shortname": ":exclamation:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "surprise"
+ "surprise",
+ "symbol",
+ "punctuation"
],
"moji": "❗"
},
@@ -7342,7 +7944,7 @@
"unicode_alternates": [],
"name": "expressionless face",
"shortname": ":expressionless:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [
"-_-",
@@ -7356,7 +7958,11 @@
"vapid",
"without expression",
"face",
- "indifferent"
+ "indifferent",
+ "mad",
+ "smiley",
+ "neutral",
+ "emotion"
],
"moji": "😑"
},
@@ -7371,25 +7977,36 @@
"keywords": [
"look",
"peek",
- "watch"
- ]
+ "watch",
+ "body",
+ "eyes"
+ ],
+ "moji": "👁"
},
"eye_in_speech_bubble": {
"unicode": "1F441-1F5E8",
- "unicode_alternates": "1f441-200d-1f5e8",
+ "unicode_alternates": [
+ "1F441-200D-1F5E8"
+ ],
"name": "eye in speech bubble",
"shortname": ":eye_in_speech_bubble:",
"category": "symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "object",
+ "symbol",
+ "eyes",
+ "talk"
+ ],
+ "moji": "👁‍🗨"
},
"eyeglasses": {
"unicode": "1F453",
"unicode_alternates": [],
"name": "eyeglasses",
"shortname": ":eyeglasses:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -7408,7 +8025,8 @@
"vision",
"see",
"blurry",
- "contacts"
+ "contacts",
+ "glasses"
],
"moji": "👓"
},
@@ -7417,27 +8035,110 @@
"unicode_alternates": [],
"name": "eyes",
"shortname": ":eyes:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"look",
"peek",
"stalk",
- "watch"
+ "watch",
+ "body",
+ "eyes"
],
"moji": "👀"
},
+ "face_palm": {
+ "unicode": "1F926",
+ "unicode_alternates": [],
+ "name": "face palm",
+ "shortname": ":face_palm:",
+ "category": "people",
+ "aliases": [
+ ":facepalm:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤦"
+ },
+ "face_palm_tone1": {
+ "unicode": "1F926-1F3FB",
+ "unicode_alternates": [],
+ "name": "face palm tone 1",
+ "shortname": ":face_palm_tone1:",
+ "category": "people",
+ "aliases": [
+ ":facepalm_tone1:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤦🏻"
+ },
+ "face_palm_tone2": {
+ "unicode": "1F926-1F3FC",
+ "unicode_alternates": [],
+ "name": "face palm tone 2",
+ "shortname": ":face_palm_tone2:",
+ "category": "people",
+ "aliases": [
+ ":facepalm_tone2:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤦🏼"
+ },
+ "face_palm_tone3": {
+ "unicode": "1F926-1F3FD",
+ "unicode_alternates": [],
+ "name": "face palm tone 3",
+ "shortname": ":face_palm_tone3:",
+ "category": "people",
+ "aliases": [
+ ":facepalm_tone3:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤦🏽"
+ },
+ "face_palm_tone4": {
+ "unicode": "1F926-1F3FE",
+ "unicode_alternates": [],
+ "name": "face palm tone 4",
+ "shortname": ":face_palm_tone4:",
+ "category": "people",
+ "aliases": [
+ ":facepalm_tone4:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤦🏾"
+ },
+ "face_palm_tone5": {
+ "unicode": "1F926-1F3FF",
+ "unicode_alternates": [],
+ "name": "face palm tone 5",
+ "shortname": ":face_palm_tone5:",
+ "category": "people",
+ "aliases": [
+ ":facepalm_tone5:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤦🏿"
+ },
"factory": {
"unicode": "1F3ED",
"unicode_alternates": [],
"name": "factory",
"shortname": ":factory:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "building"
+ "building",
+ "places",
+ "travel",
+ "steam"
],
"moji": "🏭"
},
@@ -7467,7 +8168,7 @@
"unicode_alternates": [],
"name": "family",
"shortname": ":family:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -7479,13 +8180,12 @@
"mother",
"parents",
"family",
- "mother",
- "father",
- "child",
"girl",
"boy",
"group",
- "unit"
+ "unit",
+ "people",
+ "baby"
],
"moji": "👪"
},
@@ -7509,8 +8209,14 @@
"gay",
"homosexual",
"man",
- "boy"
- ]
+ "boy",
+ "people",
+ "family",
+ "men",
+ "baby",
+ "lgbt"
+ ],
+ "moji": "👨‍👨‍👦"
},
"family_mmbb": {
"unicode": "1F468-1F468-1F466-1F466",
@@ -7532,8 +8238,14 @@
"gay",
"homosexual",
"man",
- "boy"
- ]
+ "boy",
+ "people",
+ "family",
+ "men",
+ "baby",
+ "lgbt"
+ ],
+ "moji": "👨‍👨‍👦‍👦"
},
"family_mmg": {
"unicode": "1F468-1F468-1F467",
@@ -7555,8 +8267,14 @@
"gay",
"homosexual",
"man",
- "girl"
- ]
+ "girl",
+ "people",
+ "family",
+ "men",
+ "baby",
+ "lgbt"
+ ],
+ "moji": "👨‍👨‍👧"
},
"family_mmgb": {
"unicode": "1F468-1F468-1F467-1F466",
@@ -7579,8 +8297,14 @@
"homosexual",
"man",
"girl",
- "boy"
- ]
+ "boy",
+ "people",
+ "family",
+ "men",
+ "baby",
+ "lgbt"
+ ],
+ "moji": "👨‍👨‍👧‍👦"
},
"family_mmgg": {
"unicode": "1F468-1F468-1F467-1F467",
@@ -7602,8 +8326,14 @@
"gay",
"homosexual",
"man",
- "girl"
- ]
+ "girl",
+ "people",
+ "family",
+ "men",
+ "baby",
+ "lgbt"
+ ],
+ "moji": "👨‍👨‍👧‍👧"
},
"family_mwbb": {
"unicode": "1F468-1F469-1F466-1F466",
@@ -7626,8 +8356,12 @@
"group",
"unit",
"man",
- "woman"
- ]
+ "woman",
+ "people",
+ "family",
+ "baby"
+ ],
+ "moji": "👨‍👩‍👦‍👦"
},
"family_mwg": {
"unicode": "1F468-1F469-1F467",
@@ -7651,8 +8385,12 @@
"group",
"unit",
"man",
- "woman"
- ]
+ "woman",
+ "people",
+ "family",
+ "baby"
+ ],
+ "moji": "👨‍👩‍👧"
},
"family_mwgb": {
"unicode": "1F468-1F469-1F467-1F466",
@@ -7676,8 +8414,12 @@
"group",
"unit",
"man",
- "woman"
- ]
+ "woman",
+ "people",
+ "family",
+ "baby"
+ ],
+ "moji": "👨‍👩‍👧‍👦"
},
"family_mwgg": {
"unicode": "1F468-1F469-1F467-1F467",
@@ -7700,8 +8442,12 @@
"group",
"unit",
"man",
- "woman"
- ]
+ "woman",
+ "people",
+ "family",
+ "baby"
+ ],
+ "moji": "👨‍👩‍👧‍👧"
},
"family_wwb": {
"unicode": "1F469-1F469-1F466",
@@ -7724,8 +8470,14 @@
"gay",
"lesbian",
"homosexual",
- "woman"
- ]
+ "woman",
+ "people",
+ "family",
+ "women",
+ "baby",
+ "lgbt"
+ ],
+ "moji": "👩‍👩‍👦"
},
"family_wwbb": {
"unicode": "1F469-1F469-1F466-1F466",
@@ -7748,8 +8500,14 @@
"lesbian",
"homosexual",
"woman",
- "boy"
- ]
+ "boy",
+ "people",
+ "family",
+ "women",
+ "baby",
+ "lgbt"
+ ],
+ "moji": "👩‍👩‍👦‍👦"
},
"family_wwg": {
"unicode": "1F469-1F469-1F467",
@@ -7772,8 +8530,14 @@
"unit",
"gay",
"lesbian",
- "homosexual"
- ]
+ "homosexual",
+ "people",
+ "family",
+ "women",
+ "baby",
+ "lgbt"
+ ],
+ "moji": "👩‍👩‍👧"
},
"family_wwgb": {
"unicode": "1F469-1F469-1F467-1F466",
@@ -7797,8 +8561,14 @@
"homosexual",
"woman",
"girl",
- "boy"
- ]
+ "boy",
+ "people",
+ "family",
+ "women",
+ "baby",
+ "lgbt"
+ ],
+ "moji": "👩‍👩‍👧‍👦"
},
"family_wwgg": {
"unicode": "1F469-1F469-1F467-1F467",
@@ -7821,19 +8591,27 @@
"lesbian",
"homosexual",
"woman",
- "girl"
- ]
+ "girl",
+ "people",
+ "family",
+ "women",
+ "baby",
+ "lgbt"
+ ],
+ "moji": "👩‍👩‍👧‍👧"
},
"fast_forward": {
"unicode": "23E9",
"unicode_alternates": [],
"name": "black right-pointing double triangle",
"shortname": ":fast_forward:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "blue-square"
+ "blue-square",
+ "arrow",
+ "symbol"
],
"moji": "⏩"
},
@@ -7847,7 +8625,10 @@
"aliases_ascii": [],
"keywords": [
"communication",
- "technology"
+ "technology",
+ "electronics",
+ "work",
+ "office"
],
"moji": "📠"
},
@@ -7856,7 +8637,7 @@
"unicode_alternates": [],
"name": "fearful face",
"shortname": ":fearful:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -7867,8 +8648,10 @@
"terrified",
"fear",
"fearful",
- "scared",
- "frightened"
+ "frightened",
+ "smiley",
+ "surprised",
+ "emotion"
],
"moji": "😨"
},
@@ -7888,16 +8671,12 @@
"paw",
"pet",
"tracking",
- "paw",
"prints",
"mark",
"imprints",
"footsteps",
- "animal",
"lion",
"bear",
- "dog",
- "cat",
"raccoon",
"critter",
"feet",
@@ -7905,12 +8684,25 @@
],
"moji": "🐾"
},
+ "fencer": {
+ "unicode": "1F93A",
+ "unicode_alternates": [],
+ "name": "fencer",
+ "shortname": ":fencer:",
+ "category": "activity",
+ "aliases": [
+ ":fencing:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤺"
+ },
"ferris_wheel": {
"unicode": "1F3A1",
"unicode_alternates": [],
"name": "ferris wheel",
"shortname": ":ferris_wheel:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -7923,13 +8715,16 @@
"park",
"fair",
"ride",
- "entertainment"
+ "entertainment",
+ "places",
+ "vacation",
+ "ferris wheel"
],
"moji": "🎡"
},
"ferry": {
"unicode": "26F4",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "ferry",
"shortname": ":ferry:",
"category": "travel",
@@ -7938,33 +8733,44 @@
"keywords": [
"boat",
"place",
- "travel"
- ]
+ "travel",
+ "transportation",
+ "vacation"
+ ],
+ "moji": "⛴"
},
"field_hockey": {
"unicode": "1F3D1",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "field hockey stick and ball",
"shortname": ":field_hockey:",
"category": "activity",
"aliases": [],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "ball",
+ "sport",
+ "hockey"
+ ],
+ "moji": "🏑"
},
"file_cabinet": {
"unicode": "1F5C4",
"unicode_alternates": [],
"name": "file cabinet",
"shortname": ":file_cabinet:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"folders",
"office",
"documents",
- "storage"
- ]
+ "storage",
+ "object",
+ "work"
+ ],
+ "moji": "🗄"
},
"file_folder": {
"unicode": "1F4C1",
@@ -7975,7 +8781,9 @@
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "documents"
+ "documents",
+ "work",
+ "office"
],
"moji": "📁"
},
@@ -7984,7 +8792,7 @@
"unicode_alternates": [],
"name": "film frames",
"shortname": ":film_frames:",
- "category": "activity",
+ "category": "objects",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -7993,95 +8801,96 @@
"8mm",
"16mm",
"reel",
- "celluloid"
- ]
+ "celluloid",
+ "object",
+ "camera"
+ ],
+ "moji": "🎞"
},
- "finger_pointing_down": {
- "unicode": "1F597",
+ "fingers_crossed": {
+ "unicode": "1F91E",
"unicode_alternates": [],
- "name": "white down pointing left hand index",
- "shortname": ":finger_pointing_down:",
+ "name": "hand with first and index finger crossed",
+ "shortname": ":fingers_crossed:",
"category": "people",
"aliases": [
- ":white_down_pointing_left_hand_index:"
+ ":hand_with_index_and_middle_finger_crossed:"
],
"aliases_ascii": [],
- "keywords": [
- "direction",
- "finger",
- "hand"
- ]
+ "keywords": [],
+ "moji": "🤞"
},
- "finger_pointing_down2": {
- "unicode": "1F59F",
+ "fingers_crossed_tone1": {
+ "unicode": "1F91E-1F3FB",
"unicode_alternates": [],
- "name": "sideways white down pointing index",
- "shortname": ":finger_pointing_down2:",
+ "name": "hand with index and middle fingers crossed tone 1",
+ "shortname": ":fingers_crossed_tone1:",
"category": "people",
"aliases": [
- ":sideways_white_down_pointing_index:"
+ ":hand_with_index_and_middle_fingers_crossed_tone1:"
],
"aliases_ascii": [],
- "keywords": [
- "direction",
- "finger",
- "hand"
- ]
+ "keywords": [],
+ "moji": "🤞🏻"
},
- "finger_pointing_left": {
- "unicode": "1F598",
+ "fingers_crossed_tone2": {
+ "unicode": "1F91E-1F3FC",
"unicode_alternates": [],
- "name": "sideways white left pointing index",
- "shortname": ":finger_pointing_left:",
+ "name": "hand with index and middle fingers crossed tone 2",
+ "shortname": ":fingers_crossed_tone2:",
"category": "people",
"aliases": [
- ":sideways_white_left_pointing_index:"
+ ":hand_with_index_and_middle_fingers_crossed_tone2:"
],
"aliases_ascii": [],
- "keywords": [
- "direction",
- "finger",
- "hand"
- ]
+ "keywords": [],
+ "moji": "🤞🏼"
},
- "finger_pointing_right": {
- "unicode": "1F599",
+ "fingers_crossed_tone3": {
+ "unicode": "1F91E-1F3FD",
"unicode_alternates": [],
- "name": "sideways white right pointing index",
- "shortname": ":finger_pointing_right:",
+ "name": "hand with index and middle fingers crossed tone 3",
+ "shortname": ":fingers_crossed_tone3:",
"category": "people",
"aliases": [
- ":sideways_white_right_pointing_index:"
+ ":hand_with_index_and_middle_fingers_crossed_tone3:"
],
"aliases_ascii": [],
- "keywords": [
- "direction",
- "finger",
- "hand"
- ]
+ "keywords": [],
+ "moji": "🤞🏽"
},
- "finger_pointing_up": {
- "unicode": "1F59E",
+ "fingers_crossed_tone4": {
+ "unicode": "1F91E-1F3FE",
"unicode_alternates": [],
- "name": "sideways white up pointing index",
- "shortname": ":finger_pointing_up:",
+ "name": "hand with index and middle fingers crossed tone 4",
+ "shortname": ":fingers_crossed_tone4:",
"category": "people",
"aliases": [
- ":sideways_white_up_pointing_index:"
+ ":hand_with_index_and_middle_fingers_crossed_tone4:"
],
"aliases_ascii": [],
- "keywords": [
- "direction",
- "finger",
- "hand"
- ]
+ "keywords": [],
+ "moji": "🤞🏾"
+ },
+ "fingers_crossed_tone5": {
+ "unicode": "1F91E-1F3FF",
+ "unicode_alternates": [],
+ "name": "hand with index and middle fingers crossed tone 5",
+ "shortname": ":fingers_crossed_tone5:",
+ "category": "people",
+ "aliases": [
+ ":hand_with_index_and_middle_fingers_crossed_tone5:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤞🏿"
},
"fire": {
"unicode": "1F525",
"unicode_alternates": [],
"name": "fire",
"shortname": ":fire:",
- "category": "emoticons",
+ "category": "nature",
"aliases": [
":flame:"
],
@@ -8089,7 +8898,8 @@
"keywords": [
"cook",
"hot",
- "flame"
+ "flame",
+ "wth"
],
"moji": "🔥"
},
@@ -8098,7 +8908,7 @@
"unicode_alternates": [],
"name": "fire engine",
"shortname": ":fire_engine:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -8110,34 +8920,17 @@
"engine",
"truck",
"emergency",
- "medical"
+ "medical",
+ "911"
],
"moji": "🚒"
},
- "fire_engine_oncoming": {
- "unicode": "1F6F1",
- "unicode_alternates": [],
- "name": "oncoming fire engine",
- "shortname": ":fire_engine_oncoming:",
- "category": "travel_places",
- "aliases": [
- ":oncoming_fire_engine:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "transportation",
- "vehicle",
- "fighter",
- "truck",
- "emergency"
- ]
- },
"fireworks": {
"unicode": "1F386",
"unicode_alternates": [],
"name": "fireworks",
"shortname": ":fireworks:",
- "category": "objects",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -8154,10 +8947,24 @@
"rocket",
"sky",
"idea",
- "excitement"
+ "excitement",
+ "parties"
],
"moji": "🎆"
},
+ "first_place": {
+ "unicode": "1F947",
+ "unicode_alternates": [],
+ "name": "first place medal",
+ "shortname": ":first_place:",
+ "category": "activity",
+ "aliases": [
+ ":first_place_medal:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🥇"
+ },
"first_quarter_moon": {
"unicode": "1F313",
"unicode_alternates": [],
@@ -8174,7 +8981,8 @@
"sky",
"night",
"cheese",
- "phase"
+ "phase",
+ "space"
],
"moji": "🌓"
},
@@ -8196,7 +9004,8 @@
"sky",
"night",
"cheese",
- "phase"
+ "phase",
+ "space"
],
"moji": "🌛"
},
@@ -8211,7 +9020,8 @@
"keywords": [
"animal",
"food",
- "nature"
+ "nature",
+ "wildlife"
],
"moji": "🐟"
},
@@ -8220,7 +9030,7 @@
"unicode_alternates": [],
"name": "fish cake with swirl design",
"shortname": ":fish_cake:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -8231,7 +9041,8 @@
"swirl",
"ramen",
"noodles",
- "naruto"
+ "naruto",
+ "sushi"
],
"moji": "🍥"
},
@@ -8240,7 +9051,7 @@
"unicode_alternates": [],
"name": "fishing pole and fish",
"shortname": ":fishing_pole_and_fish:",
- "category": "objects",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -8248,7 +9059,9 @@
"hobby",
"fish",
"fishing",
- "pole"
+ "pole",
+ "vacation",
+ "sport"
],
"moji": "🎣"
},
@@ -8257,19 +9070,25 @@
"unicode_alternates": [],
"name": "raised fist",
"shortname": ":fist:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"fingers",
"grasp",
- "hand"
+ "hand",
+ "body",
+ "hands",
+ "hi",
+ "fist bump",
+ "diversity",
+ "condolence"
],
"moji": "✊"
},
"fist_tone1": {
"unicode": "270A-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "raised fist tone 1",
"shortname": ":fist_tone1:",
"category": "people",
@@ -8279,11 +9098,12 @@
"fingers",
"grasp",
"hand"
- ]
+ ],
+ "moji": "✊🏻"
},
"fist_tone2": {
"unicode": "270A-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "raised fist tone 2",
"shortname": ":fist_tone2:",
"category": "people",
@@ -8293,11 +9113,12 @@
"fingers",
"grasp",
"hand"
- ]
+ ],
+ "moji": "✊🏼"
},
"fist_tone3": {
"unicode": "270A-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "raised fist tone 3",
"shortname": ":fist_tone3:",
"category": "people",
@@ -8307,11 +9128,12 @@
"fingers",
"grasp",
"hand"
- ]
+ ],
+ "moji": "✊🏽"
},
"fist_tone4": {
"unicode": "270A-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "raised fist tone 4",
"shortname": ":fist_tone4:",
"category": "people",
@@ -8321,11 +9143,12 @@
"fingers",
"grasp",
"hand"
- ]
+ ],
+ "moji": "✊🏾"
},
"fist_tone5": {
"unicode": "270A-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "raised fist tone 5",
"shortname": ":fist_tone5:",
"category": "people",
@@ -8335,7 +9158,8 @@
"fingers",
"grasp",
"hand"
- ]
+ ],
+ "moji": "✊🏿"
},
"five": {
"moji": "5️⃣",
@@ -8343,15 +9167,18 @@
"unicode_alternates": [
"0035-FE0F-20E3"
],
- "name": "digit five",
+ "name": "keycap digit five",
"shortname": ":five:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"blue-square",
"numbers",
- "prime"
+ "prime",
+ "number",
+ "math",
+ "symbol"
]
},
"flag_ac": {
@@ -8367,8 +9194,10 @@
"keywords": [
"country",
"nation",
- "ac"
- ]
+ "ac",
+ "flag"
+ ],
+ "moji": "🇦🇨"
},
"flag_ad": {
"unicode": "1F1E6-1F1E9",
@@ -8383,8 +9212,10 @@
"keywords": [
"country",
"nation",
- "ad"
- ]
+ "ad",
+ "flag"
+ ],
+ "moji": "🇦🇩"
},
"flag_ae": {
"unicode": "1F1E6-1F1EA",
@@ -8399,8 +9230,10 @@
"keywords": [
"country",
"nation",
- "ae"
- ]
+ "ae",
+ "flag"
+ ],
+ "moji": "🇦🇪"
},
"flag_af": {
"unicode": "1F1E6-1F1EB",
@@ -8416,8 +9249,10 @@
"country",
"nation",
"afghanestan",
- "af"
- ]
+ "af",
+ "flag"
+ ],
+ "moji": "🇦🇫"
},
"flag_ag": {
"unicode": "1F1E6-1F1EC",
@@ -8432,8 +9267,10 @@
"keywords": [
"country",
"nation",
- "ag"
- ]
+ "ag",
+ "flag"
+ ],
+ "moji": "🇦🇬"
},
"flag_ai": {
"unicode": "1F1E6-1F1EE",
@@ -8448,8 +9285,10 @@
"keywords": [
"country",
"nation",
- "ai"
- ]
+ "ai",
+ "flag"
+ ],
+ "moji": "🇦🇮"
},
"flag_al": {
"unicode": "1F1E6-1F1F1",
@@ -8465,8 +9304,10 @@
"country",
"nation",
"shqiperia",
- "al"
- ]
+ "al",
+ "flag"
+ ],
+ "moji": "🇦🇱"
},
"flag_am": {
"unicode": "1F1E6-1F1F2",
@@ -8482,8 +9323,10 @@
"country",
"nation",
"hayastan",
- "am"
- ]
+ "am",
+ "flag"
+ ],
+ "moji": "🇦🇲"
},
"flag_ao": {
"unicode": "1F1E6-1F1F4",
@@ -8498,12 +9341,14 @@
"keywords": [
"country",
"nation",
- "ao"
- ]
+ "ao",
+ "flag"
+ ],
+ "moji": "🇦🇴"
},
"flag_aq": {
"unicode": "1F1E6-1F1F6",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "antarctica",
"shortname": ":flag_aq:",
"category": "flags",
@@ -8511,7 +9356,11 @@
":aq:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇦🇶"
},
"flag_ar": {
"unicode": "1F1E6-1F1F7",
@@ -8526,12 +9375,14 @@
"keywords": [
"country",
"nation",
- "ar"
- ]
+ "ar",
+ "flag"
+ ],
+ "moji": "🇦🇷"
},
"flag_as": {
"unicode": "1F1E6-1F1F8",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "american samoa",
"shortname": ":flag_as:",
"category": "flags",
@@ -8539,7 +9390,11 @@
":as:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇦🇸"
},
"flag_at": {
"unicode": "1F1E6-1F1F9",
@@ -8556,8 +9411,10 @@
"nation",
"&ouml;sterreich",
"osterreich",
- "at"
- ]
+ "at",
+ "flag"
+ ],
+ "moji": "🇦🇹"
},
"flag_au": {
"unicode": "1F1E6-1F1FA",
@@ -8572,8 +9429,10 @@
"keywords": [
"country",
"nation",
- "au"
- ]
+ "au",
+ "flag"
+ ],
+ "moji": "🇦🇺"
},
"flag_aw": {
"unicode": "1F1E6-1F1FC",
@@ -8588,12 +9447,14 @@
"keywords": [
"country",
"nation",
- "aw"
- ]
+ "aw",
+ "flag"
+ ],
+ "moji": "🇦🇼"
},
"flag_ax": {
"unicode": "1F1E6-1F1FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "åland islands",
"shortname": ":flag_ax:",
"category": "flags",
@@ -8601,7 +9462,11 @@
":ax:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇦🇽"
},
"flag_az": {
"unicode": "1F1E6-1F1FF",
@@ -8617,8 +9482,10 @@
"country",
"nation",
"azarbaycan",
- "az"
- ]
+ "az",
+ "flag"
+ ],
+ "moji": "🇦🇿"
},
"flag_ba": {
"unicode": "1F1E7-1F1E6",
@@ -8634,8 +9501,10 @@
"country",
"nation",
"bosna i hercegovina",
- "ba"
- ]
+ "ba",
+ "flag"
+ ],
+ "moji": "🇧🇦"
},
"flag_bb": {
"unicode": "1F1E7-1F1E7",
@@ -8650,8 +9519,10 @@
"keywords": [
"country",
"nation",
- "bb"
- ]
+ "bb",
+ "flag"
+ ],
+ "moji": "🇧🇧"
},
"flag_bd": {
"unicode": "1F1E7-1F1E9",
@@ -8666,8 +9537,10 @@
"keywords": [
"country",
"nation",
- "bd"
- ]
+ "bd",
+ "flag"
+ ],
+ "moji": "🇧🇩"
},
"flag_be": {
"unicode": "1F1E7-1F1EA",
@@ -8684,8 +9557,10 @@
"nation",
"belgique",
"belgie",
- "be"
- ]
+ "be",
+ "flag"
+ ],
+ "moji": "🇧🇪"
},
"flag_bf": {
"unicode": "1F1E7-1F1EB",
@@ -8700,8 +9575,10 @@
"keywords": [
"country",
"nation",
- "bf"
- ]
+ "bf",
+ "flag"
+ ],
+ "moji": "🇧🇫"
},
"flag_bg": {
"unicode": "1F1E7-1F1EC",
@@ -8716,8 +9593,10 @@
"keywords": [
"country",
"nation",
- "bg"
- ]
+ "bg",
+ "flag"
+ ],
+ "moji": "🇧🇬"
},
"flag_bh": {
"unicode": "1F1E7-1F1ED",
@@ -8733,8 +9612,10 @@
"country",
"nation",
"al bahrayn",
- "bh"
- ]
+ "bh",
+ "flag"
+ ],
+ "moji": "🇧🇭"
},
"flag_bi": {
"unicode": "1F1E7-1F1EE",
@@ -8749,8 +9630,10 @@
"keywords": [
"country",
"nation",
- "bi"
- ]
+ "bi",
+ "flag"
+ ],
+ "moji": "🇧🇮"
},
"flag_bj": {
"unicode": "1F1E7-1F1EF",
@@ -8765,12 +9648,14 @@
"keywords": [
"country",
"nation",
- "bj"
- ]
+ "bj",
+ "flag"
+ ],
+ "moji": "🇧🇯"
},
"flag_bl": {
"unicode": "1F1E7-1F1F1",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "saint barthélemy",
"shortname": ":flag_bl:",
"category": "flags",
@@ -8778,22 +9663,28 @@
":bl:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇧🇱"
},
"flag_black": {
"unicode": "1F3F4",
"unicode_alternates": [],
"name": "waving black flag",
"shortname": ":flag_black:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [
":waving_black_flag:"
],
"aliases_ascii": [],
"keywords": [
"symbol",
- "signal"
- ]
+ "signal",
+ "object"
+ ],
+ "moji": "🏴"
},
"flag_bm": {
"unicode": "1F1E7-1F1F2",
@@ -8808,8 +9699,10 @@
"keywords": [
"country",
"nation",
- "bm"
- ]
+ "bm",
+ "flag"
+ ],
+ "moji": "🇧🇲"
},
"flag_bn": {
"unicode": "1F1E7-1F1F3",
@@ -8824,8 +9717,10 @@
"keywords": [
"country",
"nation",
- "bn"
- ]
+ "bn",
+ "flag"
+ ],
+ "moji": "🇧🇳"
},
"flag_bo": {
"unicode": "1F1E7-1F1F4",
@@ -8840,12 +9735,14 @@
"keywords": [
"country",
"nation",
- "bo"
- ]
+ "bo",
+ "flag"
+ ],
+ "moji": "🇧🇴"
},
"flag_bq": {
"unicode": "1F1E7-1F1F6",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "caribbean netherlands",
"shortname": ":flag_bq:",
"category": "flags",
@@ -8853,7 +9750,11 @@
":bq:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇧🇶"
},
"flag_br": {
"unicode": "1F1E7-1F1F7",
@@ -8869,8 +9770,10 @@
"country",
"nation",
"brasil",
- "br"
- ]
+ "br",
+ "flag"
+ ],
+ "moji": "🇧🇷"
},
"flag_bs": {
"unicode": "1F1E7-1F1F8",
@@ -8885,8 +9788,10 @@
"keywords": [
"country",
"nation",
- "bs"
- ]
+ "bs",
+ "flag"
+ ],
+ "moji": "🇧🇸"
},
"flag_bt": {
"unicode": "1F1E7-1F1F9",
@@ -8901,12 +9806,14 @@
"keywords": [
"country",
"nation",
- "bt"
- ]
+ "bt",
+ "flag"
+ ],
+ "moji": "🇧🇹"
},
"flag_bv": {
"unicode": "1F1E7-1F1FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "bouvet island",
"shortname": ":flag_bv:",
"category": "flags",
@@ -8914,7 +9821,11 @@
":bv:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇧🇻"
},
"flag_bw": {
"unicode": "1F1E7-1F1FC",
@@ -8929,8 +9840,10 @@
"keywords": [
"country",
"nation",
- "bw"
- ]
+ "bw",
+ "flag"
+ ],
+ "moji": "🇧🇼"
},
"flag_by": {
"unicode": "1F1E7-1F1FE",
@@ -8946,8 +9859,10 @@
"country",
"nation",
"byelarus",
- "by"
- ]
+ "by",
+ "flag"
+ ],
+ "moji": "🇧🇾"
},
"flag_bz": {
"unicode": "1F1E7-1F1FF",
@@ -8962,8 +9877,10 @@
"keywords": [
"country",
"nation",
- "bz"
- ]
+ "bz",
+ "flag"
+ ],
+ "moji": "🇧🇿"
},
"flag_ca": {
"unicode": "1F1E8-1F1E6",
@@ -8978,12 +9895,14 @@
"keywords": [
"country",
"nation",
- "ca"
- ]
+ "ca",
+ "flag"
+ ],
+ "moji": "🇨🇦"
},
"flag_cc": {
"unicode": "1F1E8-1F1E8",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "cocos (keeling) islands",
"shortname": ":flag_cc:",
"category": "flags",
@@ -8991,7 +9910,11 @@
":cc:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇨🇨"
},
"flag_cd": {
"unicode": "1F1E8-1F1E9",
@@ -9008,8 +9931,10 @@
"nation",
"r&eacute;publique d&eacute;mocratique du congo",
"republique democratique du congo",
- "cd"
- ]
+ "cd",
+ "flag"
+ ],
+ "moji": "🇨🇩"
},
"flag_cf": {
"unicode": "1F1E8-1F1EB",
@@ -9024,8 +9949,10 @@
"keywords": [
"country",
"nation",
- "cf"
- ]
+ "cf",
+ "flag"
+ ],
+ "moji": "🇨🇫"
},
"flag_cg": {
"unicode": "1F1E8-1F1EC",
@@ -9040,8 +9967,10 @@
"keywords": [
"country",
"nation",
- "cg"
- ]
+ "cg",
+ "flag"
+ ],
+ "moji": "🇨🇬"
},
"flag_ch": {
"unicode": "1F1E8-1F1ED",
@@ -9056,8 +9985,11 @@
"keywords": [
"country",
"nation",
- "swiss"
- ]
+ "swiss",
+ "neutral",
+ "flag"
+ ],
+ "moji": "🇨🇭"
},
"flag_ci": {
"unicode": "1F1E8-1F1EE",
@@ -9072,12 +10004,14 @@
"keywords": [
"country",
"nation",
- "ci"
- ]
+ "ci",
+ "flag"
+ ],
+ "moji": "🇨🇮"
},
"flag_ck": {
"unicode": "1F1E8-1F1F0",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "cook islands",
"shortname": ":flag_ck:",
"category": "flags",
@@ -9085,7 +10019,11 @@
":ck:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇨🇰"
},
"flag_cl": {
"unicode": "1F1E8-1F1F1",
@@ -9100,8 +10038,10 @@
"keywords": [
"country",
"nation",
- "cl"
- ]
+ "cl",
+ "flag"
+ ],
+ "moji": "🇨🇱"
},
"flag_cm": {
"unicode": "1F1E8-1F1F2",
@@ -9116,8 +10056,10 @@
"keywords": [
"country",
"nation",
- "cm"
- ]
+ "cm",
+ "flag"
+ ],
+ "moji": "🇨🇲"
},
"flag_cn": {
"unicode": "1F1E8-1F1F3",
@@ -9135,8 +10077,10 @@
"zhong guo",
"country",
"nation",
- "cn"
- ]
+ "cn",
+ "flag"
+ ],
+ "moji": "🇨🇳"
},
"flag_co": {
"unicode": "1F1E8-1F1F4",
@@ -9151,12 +10095,14 @@
"keywords": [
"country",
"nation",
- "co"
- ]
+ "co",
+ "flag"
+ ],
+ "moji": "🇨🇴"
},
"flag_cp": {
"unicode": "1F1E8-1F1F5",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "clipperton island",
"shortname": ":flag_cp:",
"category": "flags",
@@ -9164,7 +10110,11 @@
":cp:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇨🇵"
},
"flag_cr": {
"unicode": "1F1E8-1F1F7",
@@ -9179,8 +10129,10 @@
"keywords": [
"country",
"nation",
- "cr"
- ]
+ "cr",
+ "flag"
+ ],
+ "moji": "🇨🇷"
},
"flag_cu": {
"unicode": "1F1E8-1F1FA",
@@ -9195,8 +10147,10 @@
"keywords": [
"country",
"nation",
- "cu"
- ]
+ "cu",
+ "flag"
+ ],
+ "moji": "🇨🇺"
},
"flag_cv": {
"unicode": "1F1E8-1F1FB",
@@ -9212,12 +10166,14 @@
"country",
"nation",
"cabo verde",
- "cv"
- ]
+ "cv",
+ "flag"
+ ],
+ "moji": "🇨🇻"
},
"flag_cw": {
"unicode": "1F1E8-1F1FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "curaçao",
"shortname": ":flag_cw:",
"category": "flags",
@@ -9225,11 +10181,15 @@
":cw:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇨🇼"
},
"flag_cx": {
"unicode": "1F1E8-1F1FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "christmas island",
"shortname": ":flag_cx:",
"category": "flags",
@@ -9237,7 +10197,11 @@
":cx:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇨🇽"
},
"flag_cy": {
"unicode": "1F1E8-1F1FE",
@@ -9254,8 +10218,10 @@
"nation",
"kibris",
"kypros",
- "cy"
- ]
+ "cy",
+ "flag"
+ ],
+ "moji": "🇨🇾"
},
"flag_cz": {
"unicode": "1F1E8-1F1FF",
@@ -9271,8 +10237,10 @@
"country",
"nation",
"ceska republika",
- "cz"
- ]
+ "cz",
+ "flag"
+ ],
+ "moji": "🇨🇿"
},
"flag_de": {
"unicode": "1F1E9-1F1EA",
@@ -9289,12 +10257,14 @@
"nation",
"deutschland",
"country",
- "de"
- ]
+ "de",
+ "flag"
+ ],
+ "moji": "🇩🇪"
},
"flag_dg": {
"unicode": "1F1E9-1F1EC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "diego garcia",
"shortname": ":flag_dg:",
"category": "flags",
@@ -9302,7 +10272,11 @@
":dg:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇩🇬"
},
"flag_dj": {
"unicode": "1F1E9-1F1EF",
@@ -9317,8 +10291,10 @@
"keywords": [
"country",
"nation",
- "dj"
- ]
+ "dj",
+ "flag"
+ ],
+ "moji": "🇩🇯"
},
"flag_dk": {
"unicode": "1F1E9-1F1F0",
@@ -9334,8 +10310,10 @@
"country",
"nation",
"danmark",
- "dk"
- ]
+ "dk",
+ "flag"
+ ],
+ "moji": "🇩🇰"
},
"flag_dm": {
"unicode": "1F1E9-1F1F2",
@@ -9350,8 +10328,10 @@
"keywords": [
"country",
"nation",
- "dm"
- ]
+ "dm",
+ "flag"
+ ],
+ "moji": "🇩🇲"
},
"flag_do": {
"unicode": "1F1E9-1F1F4",
@@ -9366,8 +10346,10 @@
"keywords": [
"country",
"nation",
- "do"
- ]
+ "do",
+ "flag"
+ ],
+ "moji": "🇩🇴"
},
"flag_dz": {
"unicode": "1F1E9-1F1FF",
@@ -9384,12 +10366,14 @@
"nation",
"al jaza'ir",
"al jazair",
- "dz"
- ]
+ "dz",
+ "flag"
+ ],
+ "moji": "🇩🇿"
},
"flag_ea": {
"unicode": "1F1EA-1F1E6",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "ceuta, melilla",
"shortname": ":flag_ea:",
"category": "flags",
@@ -9397,7 +10381,11 @@
":ea:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇪🇦"
},
"flag_ec": {
"unicode": "1F1EA-1F1E8",
@@ -9412,8 +10400,10 @@
"keywords": [
"country",
"nation",
- "ec"
- ]
+ "ec",
+ "flag"
+ ],
+ "moji": "🇪🇨"
},
"flag_ee": {
"unicode": "1F1EA-1F1EA",
@@ -9429,8 +10419,10 @@
"country",
"nation",
"eesti vabariik",
- "ee"
- ]
+ "ee",
+ "flag"
+ ],
+ "moji": "🇪🇪"
},
"flag_eg": {
"unicode": "1F1EA-1F1EC",
@@ -9446,8 +10438,10 @@
"country",
"nation",
"misr",
- "eg"
- ]
+ "eg",
+ "flag"
+ ],
+ "moji": "🇪🇬"
},
"flag_eh": {
"unicode": "1F1EA-1F1ED",
@@ -9465,8 +10459,10 @@
"aṣ-Ṣaḥrā’ al-gharbīyah",
"sahra",
"gharbiyah",
- "eh"
- ]
+ "eh",
+ "flag"
+ ],
+ "moji": "🇪🇭"
},
"flag_er": {
"unicode": "1F1EA-1F1F7",
@@ -9482,8 +10478,10 @@
"country",
"nation",
"hagere ertra",
- "er"
- ]
+ "er",
+ "flag"
+ ],
+ "moji": "🇪🇷"
},
"flag_es": {
"unicode": "1F1EA-1F1F8",
@@ -9500,8 +10498,10 @@
"espa&ntilde;a",
"country",
"espana",
- "es"
- ]
+ "es",
+ "flag"
+ ],
+ "moji": "🇪🇸"
},
"flag_et": {
"unicode": "1F1EA-1F1F9",
@@ -9518,12 +10518,14 @@
"nation",
"ityop'iya",
"ityopiya",
- "et"
- ]
+ "et",
+ "flag"
+ ],
+ "moji": "🇪🇹"
},
"flag_eu": {
"unicode": "1F1EA-1F1FA",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "european union",
"shortname": ":flag_eu:",
"category": "flags",
@@ -9531,7 +10533,11 @@
":eu:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇪🇺"
},
"flag_fi": {
"unicode": "1F1EB-1F1EE",
@@ -9547,8 +10553,10 @@
"country",
"nation",
"suomen tasavalta",
- "fi"
- ]
+ "fi",
+ "flag"
+ ],
+ "moji": "🇫🇮"
},
"flag_fj": {
"unicode": "1F1EB-1F1EF",
@@ -9563,8 +10571,10 @@
"keywords": [
"country",
"nation",
- "fj"
- ]
+ "fj",
+ "flag"
+ ],
+ "moji": "🇫🇯"
},
"flag_fk": {
"unicode": "1F1EB-1F1F0",
@@ -9580,8 +10590,10 @@
"country",
"nation",
"islas malvinas",
- "fk"
- ]
+ "fk",
+ "flag"
+ ],
+ "moji": "🇫🇰"
},
"flag_fm": {
"unicode": "1F1EB-1F1F2",
@@ -9596,8 +10608,10 @@
"keywords": [
"country",
"nation",
- "fm"
- ]
+ "fm",
+ "flag"
+ ],
+ "moji": "🇫🇲"
},
"flag_fo": {
"unicode": "1F1EB-1F1F4",
@@ -9613,8 +10627,10 @@
"country",
"nation",
"foroyar",
- "fo"
- ]
+ "fo",
+ "flag"
+ ],
+ "moji": "🇫🇴"
},
"flag_fr": {
"unicode": "1F1EB-1F1F7",
@@ -9630,8 +10646,10 @@
"french",
"nation",
"country",
- "fr"
- ]
+ "fr",
+ "flag"
+ ],
+ "moji": "🇫🇷"
},
"flag_ga": {
"unicode": "1F1EC-1F1E6",
@@ -9646,8 +10664,10 @@
"keywords": [
"country",
"nation",
- "ga"
- ]
+ "ga",
+ "flag"
+ ],
+ "moji": "🇬🇦"
},
"flag_gb": {
"unicode": "1F1EC-1F1E7",
@@ -9666,8 +10686,10 @@
"nation",
"united kingdom",
"england",
- "country"
- ]
+ "country",
+ "flag"
+ ],
+ "moji": "🇬🇧"
},
"flag_gd": {
"unicode": "1F1EC-1F1E9",
@@ -9682,8 +10704,10 @@
"keywords": [
"country",
"nation",
- "gd"
- ]
+ "gd",
+ "flag"
+ ],
+ "moji": "🇬🇩"
},
"flag_ge": {
"unicode": "1F1EC-1F1EA",
@@ -9700,12 +10724,14 @@
"nation",
"sak'art'velo",
"sakartvelo",
- "ge"
- ]
+ "ge",
+ "flag"
+ ],
+ "moji": "🇬🇪"
},
"flag_gf": {
"unicode": "1F1EC-1F1EB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "french guiana",
"shortname": ":flag_gf:",
"category": "flags",
@@ -9713,11 +10739,15 @@
":gf:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇬🇫"
},
"flag_gg": {
"unicode": "1F1EC-1F1EC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "guernsey",
"shortname": ":flag_gg:",
"category": "flags",
@@ -9725,7 +10755,11 @@
":gg:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇬🇬"
},
"flag_gh": {
"unicode": "1F1EC-1F1ED",
@@ -9740,8 +10774,10 @@
"keywords": [
"country",
"nation",
- "gh"
- ]
+ "gh",
+ "flag"
+ ],
+ "moji": "🇬🇭"
},
"flag_gi": {
"unicode": "1F1EC-1F1EE",
@@ -9756,8 +10792,10 @@
"keywords": [
"country",
"nation",
- "gi"
- ]
+ "gi",
+ "flag"
+ ],
+ "moji": "🇬🇮"
},
"flag_gl": {
"unicode": "1F1EC-1F1F1",
@@ -9773,8 +10811,10 @@
"country",
"nation",
"kalaallit nunaat",
- "gl"
- ]
+ "gl",
+ "flag"
+ ],
+ "moji": "🇬🇱"
},
"flag_gm": {
"unicode": "1F1EC-1F1F2",
@@ -9789,8 +10829,10 @@
"keywords": [
"country",
"nation",
- "gm"
- ]
+ "gm",
+ "flag"
+ ],
+ "moji": "🇬🇲"
},
"flag_gn": {
"unicode": "1F1EC-1F1F3",
@@ -9806,12 +10848,14 @@
"country",
"nation",
"guinee",
- "gn"
- ]
+ "gn",
+ "flag"
+ ],
+ "moji": "🇬🇳"
},
"flag_gp": {
"unicode": "1F1EC-1F1F5",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "guadeloupe",
"shortname": ":flag_gp:",
"category": "flags",
@@ -9819,7 +10863,11 @@
":gp:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇬🇵"
},
"flag_gq": {
"unicode": "1F1EC-1F1F6",
@@ -9835,8 +10883,10 @@
"country",
"nation",
"guinea ecuatorial",
- "gq"
- ]
+ "gq",
+ "flag"
+ ],
+ "moji": "🇬🇶"
},
"flag_gr": {
"unicode": "1F1EC-1F1F7",
@@ -9853,12 +10903,14 @@
"nation",
"ellas",
"ellada",
- "gr"
- ]
+ "gr",
+ "flag"
+ ],
+ "moji": "🇬🇷"
},
"flag_gs": {
"unicode": "1F1EC-1F1F8",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "south georgia",
"shortname": ":flag_gs:",
"category": "flags",
@@ -9866,7 +10918,11 @@
":gs:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇬🇸"
},
"flag_gt": {
"unicode": "1F1EC-1F1F9",
@@ -9881,8 +10937,10 @@
"keywords": [
"country",
"nation",
- "gt"
- ]
+ "gt",
+ "flag"
+ ],
+ "moji": "🇬🇹"
},
"flag_gu": {
"unicode": "1F1EC-1F1FA",
@@ -9897,8 +10955,10 @@
"keywords": [
"country",
"nation",
- "gu"
- ]
+ "gu",
+ "flag"
+ ],
+ "moji": "🇬🇺"
},
"flag_gw": {
"unicode": "1F1EC-1F1FC",
@@ -9915,8 +10975,10 @@
"nation",
"guine-bissau",
"guine bissau",
- "gw"
- ]
+ "gw",
+ "flag"
+ ],
+ "moji": "🇬🇼"
},
"flag_gy": {
"unicode": "1F1EC-1F1FE",
@@ -9931,8 +10993,10 @@
"keywords": [
"country",
"nation",
- "gy"
- ]
+ "gy",
+ "flag"
+ ],
+ "moji": "🇬🇾"
},
"flag_hk": {
"unicode": "1F1ED-1F1F0",
@@ -9948,12 +11012,14 @@
"country",
"nation",
"xianggang",
- "hk"
- ]
+ "hk",
+ "flag"
+ ],
+ "moji": "🇭🇰"
},
"flag_hm": {
"unicode": "1F1ED-1F1F2",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "heard island and mcdonald islands",
"shortname": ":flag_hm:",
"category": "flags",
@@ -9961,7 +11027,11 @@
":hm:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇭🇲"
},
"flag_hn": {
"unicode": "1F1ED-1F1F3",
@@ -9976,8 +11046,10 @@
"keywords": [
"country",
"nation",
- "hn"
- ]
+ "hn",
+ "flag"
+ ],
+ "moji": "🇭🇳"
},
"flag_hr": {
"unicode": "1F1ED-1F1F7",
@@ -9993,8 +11065,10 @@
"country",
"nation",
"hrvatska",
- "hr"
- ]
+ "hr",
+ "flag"
+ ],
+ "moji": "🇭🇷"
},
"flag_ht": {
"unicode": "1F1ED-1F1F9",
@@ -10009,8 +11083,10 @@
"keywords": [
"country",
"nation",
- "ht"
- ]
+ "ht",
+ "flag"
+ ],
+ "moji": "🇭🇹"
},
"flag_hu": {
"unicode": "1F1ED-1F1FA",
@@ -10026,12 +11102,14 @@
"country",
"nation",
"magyarorszag",
- "hu"
- ]
+ "hu",
+ "flag"
+ ],
+ "moji": "🇭🇺"
},
"flag_ic": {
"unicode": "1F1EE-1F1E8",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "canary islands",
"shortname": ":flag_ic:",
"category": "flags",
@@ -10039,7 +11117,11 @@
":ic:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇮🇨"
},
"flag_id": {
"unicode": "1F1EE-1F1E9",
@@ -10054,8 +11136,10 @@
"keywords": [
"country",
"nation",
- "id"
- ]
+ "id",
+ "flag"
+ ],
+ "moji": "🇮🇩"
},
"flag_ie": {
"unicode": "1F1EE-1F1EA",
@@ -10072,8 +11156,10 @@
"nation",
"&eacute;ire",
"eire",
- "ie"
- ]
+ "ie",
+ "flag"
+ ],
+ "moji": "🇮🇪"
},
"flag_il": {
"unicode": "1F1EE-1F1F1",
@@ -10090,12 +11176,15 @@
"nation",
"yisra'el",
"yisrael",
- "il"
- ]
+ "il",
+ "jew",
+ "flag"
+ ],
+ "moji": "🇮🇱"
},
"flag_im": {
"unicode": "1F1EE-1F1F2",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "isle of man",
"shortname": ":flag_im:",
"category": "flags",
@@ -10103,7 +11192,11 @@
":im:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇮🇲"
},
"flag_in": {
"unicode": "1F1EE-1F1F3",
@@ -10119,12 +11212,14 @@
"country",
"nation",
"bharat",
- "in"
- ]
+ "in",
+ "flag"
+ ],
+ "moji": "🇮🇳"
},
"flag_io": {
"unicode": "1F1EE-1F1F4",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "british indian ocean territory",
"shortname": ":flag_io:",
"category": "flags",
@@ -10132,7 +11227,11 @@
":io:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇮🇴"
},
"flag_iq": {
"unicode": "1F1EE-1F1F6",
@@ -10147,8 +11246,10 @@
"keywords": [
"country",
"nation",
- "iq"
- ]
+ "iq",
+ "flag"
+ ],
+ "moji": "🇮🇶"
},
"flag_ir": {
"unicode": "1F1EE-1F1F7",
@@ -10163,8 +11264,10 @@
"keywords": [
"country",
"nation",
- "ir"
- ]
+ "ir",
+ "flag"
+ ],
+ "moji": "🇮🇷"
},
"flag_is": {
"unicode": "1F1EE-1F1F8",
@@ -10180,8 +11283,10 @@
"country",
"nation",
"lyoveldio island",
- "is"
- ]
+ "is",
+ "flag"
+ ],
+ "moji": "🇮🇸"
},
"flag_it": {
"unicode": "1F1EE-1F1F9",
@@ -10197,8 +11302,11 @@
"italia",
"country",
"nation",
- "it"
- ]
+ "it",
+ "italian",
+ "flag"
+ ],
+ "moji": "🇮🇹"
},
"flag_je": {
"unicode": "1F1EF-1F1EA",
@@ -10213,8 +11321,10 @@
"keywords": [
"country",
"nation",
- "je"
- ]
+ "je",
+ "flag"
+ ],
+ "moji": "🇯🇪"
},
"flag_jm": {
"unicode": "1F1EF-1F1F2",
@@ -10229,8 +11339,10 @@
"keywords": [
"country",
"nation",
- "jm"
- ]
+ "jm",
+ "flag"
+ ],
+ "moji": "🇯🇲"
},
"flag_jo": {
"unicode": "1F1EF-1F1F4",
@@ -10246,8 +11358,10 @@
"country",
"nation",
"al urdun",
- "jo"
- ]
+ "jo",
+ "flag"
+ ],
+ "moji": "🇯🇴"
},
"flag_jp": {
"unicode": "1F1EF-1F1F5",
@@ -10263,8 +11377,11 @@
"nation",
"nippon",
"country",
- "jp"
- ]
+ "jp",
+ "japan",
+ "flag"
+ ],
+ "moji": "🇯🇵"
},
"flag_ke": {
"unicode": "1F1F0-1F1EA",
@@ -10279,8 +11396,10 @@
"keywords": [
"country",
"nation",
- "ke"
- ]
+ "ke",
+ "flag"
+ ],
+ "moji": "🇰🇪"
},
"flag_kg": {
"unicode": "1F1F0-1F1EC",
@@ -10296,8 +11415,10 @@
"country",
"nation",
"kyrgyz respublikasy",
- "kg"
- ]
+ "kg",
+ "flag"
+ ],
+ "moji": "🇰🇬"
},
"flag_kh": {
"unicode": "1F1F0-1F1ED",
@@ -10313,8 +11434,10 @@
"country",
"nation",
"kampuchea",
- "kh"
- ]
+ "kh",
+ "flag"
+ ],
+ "moji": "🇰🇭"
},
"flag_ki": {
"unicode": "1F1F0-1F1EE",
@@ -10331,8 +11454,10 @@
"nation",
"kiribati",
"kiribas",
- "ki"
- ]
+ "ki",
+ "flag"
+ ],
+ "moji": "🇰🇮"
},
"flag_km": {
"unicode": "1F1F0-1F1F2",
@@ -10347,8 +11472,10 @@
"keywords": [
"country",
"nation",
- "km"
- ]
+ "km",
+ "flag"
+ ],
+ "moji": "🇰🇲"
},
"flag_kn": {
"unicode": "1F1F0-1F1F3",
@@ -10363,8 +11490,10 @@
"keywords": [
"country",
"nation",
- "kn"
- ]
+ "kn",
+ "flag"
+ ],
+ "moji": "🇰🇳"
},
"flag_kp": {
"unicode": "1F1F0-1F1F5",
@@ -10379,8 +11508,10 @@
"keywords": [
"country",
"nation",
- "kp"
- ]
+ "kp",
+ "flag"
+ ],
+ "moji": "🇰🇵"
},
"flag_kr": {
"unicode": "1F1F0-1F1F7",
@@ -10396,8 +11527,10 @@
"nation",
"country",
"south korea",
- "kr"
- ]
+ "kr",
+ "flag"
+ ],
+ "moji": "🇰🇷"
},
"flag_kw": {
"unicode": "1F1F0-1F1FC",
@@ -10413,8 +11546,10 @@
"country",
"nation",
"al kuwayt",
- "kw"
- ]
+ "kw",
+ "flag"
+ ],
+ "moji": "🇰🇼"
},
"flag_ky": {
"unicode": "1F1F0-1F1FE",
@@ -10429,8 +11564,10 @@
"keywords": [
"country",
"nation",
- "ky"
- ]
+ "ky",
+ "flag"
+ ],
+ "moji": "🇰🇾"
},
"flag_kz": {
"unicode": "1F1F0-1F1FF",
@@ -10446,8 +11583,10 @@
"country",
"nation",
"qazaqstan",
- "kz"
- ]
+ "kz",
+ "flag"
+ ],
+ "moji": "🇰🇿"
},
"flag_la": {
"unicode": "1F1F1-1F1E6",
@@ -10462,8 +11601,10 @@
"keywords": [
"country",
"nation",
- "la"
- ]
+ "la",
+ "flag"
+ ],
+ "moji": "🇱🇦"
},
"flag_lb": {
"unicode": "1F1F1-1F1E7",
@@ -10479,8 +11620,10 @@
"country",
"nation",
"lubnan",
- "lb"
- ]
+ "lb",
+ "flag"
+ ],
+ "moji": "🇱🇧"
},
"flag_lc": {
"unicode": "1F1F1-1F1E8",
@@ -10495,8 +11638,10 @@
"keywords": [
"country",
"nation",
- "lc"
- ]
+ "lc",
+ "flag"
+ ],
+ "moji": "🇱🇨"
},
"flag_li": {
"unicode": "1F1F1-1F1EE",
@@ -10511,8 +11656,10 @@
"keywords": [
"country",
"nation",
- "li"
- ]
+ "li",
+ "flag"
+ ],
+ "moji": "🇱🇮"
},
"flag_lk": {
"unicode": "1F1F1-1F1F0",
@@ -10527,8 +11674,10 @@
"keywords": [
"country",
"nation",
- "lk"
- ]
+ "lk",
+ "flag"
+ ],
+ "moji": "🇱🇰"
},
"flag_lr": {
"unicode": "1F1F1-1F1F7",
@@ -10543,8 +11692,10 @@
"keywords": [
"country",
"nation",
- "lr"
- ]
+ "lr",
+ "flag"
+ ],
+ "moji": "🇱🇷"
},
"flag_ls": {
"unicode": "1F1F1-1F1F8",
@@ -10559,8 +11710,10 @@
"keywords": [
"country",
"nation",
- "ls"
- ]
+ "ls",
+ "flag"
+ ],
+ "moji": "🇱🇸"
},
"flag_lt": {
"unicode": "1F1F1-1F1F9",
@@ -10576,8 +11729,10 @@
"country",
"nation",
"lietuva",
- "lt"
- ]
+ "lt",
+ "flag"
+ ],
+ "moji": "🇱🇹"
},
"flag_lu": {
"unicode": "1F1F1-1F1FA",
@@ -10594,8 +11749,10 @@
"nation",
"luxembourg",
"letzebuerg",
- "lu"
- ]
+ "lu",
+ "flag"
+ ],
+ "moji": "🇱🇺"
},
"flag_lv": {
"unicode": "1F1F1-1F1FB",
@@ -10611,8 +11768,10 @@
"country",
"nation",
"latvija",
- "lv"
- ]
+ "lv",
+ "flag"
+ ],
+ "moji": "🇱🇻"
},
"flag_ly": {
"unicode": "1F1F1-1F1FE",
@@ -10628,8 +11787,10 @@
"country",
"nation",
"libiyah",
- "ly"
- ]
+ "ly",
+ "flag"
+ ],
+ "moji": "🇱🇾"
},
"flag_ma": {
"unicode": "1F1F2-1F1E6",
@@ -10645,8 +11806,10 @@
"country",
"nation",
"al maghrib",
- "ma"
- ]
+ "ma",
+ "flag"
+ ],
+ "moji": "🇲🇦"
},
"flag_mc": {
"unicode": "1F1F2-1F1E8",
@@ -10661,8 +11824,10 @@
"keywords": [
"country",
"nation",
- "mc"
- ]
+ "mc",
+ "flag"
+ ],
+ "moji": "🇲🇨"
},
"flag_md": {
"unicode": "1F1F2-1F1E9",
@@ -10677,8 +11842,10 @@
"keywords": [
"country",
"nation",
- "md"
- ]
+ "md",
+ "flag"
+ ],
+ "moji": "🇲🇩"
},
"flag_me": {
"unicode": "1F1F2-1F1EA",
@@ -10694,12 +11861,14 @@
"country",
"nation",
"crna gora",
- "me"
- ]
+ "me",
+ "flag"
+ ],
+ "moji": "🇲🇪"
},
"flag_mf": {
"unicode": "1F1F2-1F1EB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "saint martin",
"shortname": ":flag_mf:",
"category": "flags",
@@ -10707,7 +11876,11 @@
":mf:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇲🇫"
},
"flag_mg": {
"unicode": "1F1F2-1F1EC",
@@ -10722,8 +11895,10 @@
"keywords": [
"country",
"nation",
- "mg"
- ]
+ "mg",
+ "flag"
+ ],
+ "moji": "🇲🇬"
},
"flag_mh": {
"unicode": "1F1F2-1F1ED",
@@ -10738,8 +11913,10 @@
"keywords": [
"country",
"nation",
- "mh"
- ]
+ "mh",
+ "flag"
+ ],
+ "moji": "🇲🇭"
},
"flag_mk": {
"unicode": "1F1F2-1F1F0",
@@ -10754,8 +11931,10 @@
"keywords": [
"country",
"nation",
- "mk"
- ]
+ "mk",
+ "flag"
+ ],
+ "moji": "🇲🇰"
},
"flag_ml": {
"unicode": "1F1F2-1F1F1",
@@ -10770,8 +11949,10 @@
"keywords": [
"country",
"nation",
- "ml"
- ]
+ "ml",
+ "flag"
+ ],
+ "moji": "🇲🇱"
},
"flag_mm": {
"unicode": "1F1F2-1F1F2",
@@ -10787,8 +11968,10 @@
"country",
"nation",
"myanma naingngandaw",
- "mm"
- ]
+ "mm",
+ "flag"
+ ],
+ "moji": "🇲🇲"
},
"flag_mn": {
"unicode": "1F1F2-1F1F3",
@@ -10804,8 +11987,10 @@
"country",
"nation",
"mongol uls",
- "mn"
- ]
+ "mn",
+ "flag"
+ ],
+ "moji": "🇲🇳"
},
"flag_mo": {
"unicode": "1F1F2-1F1F4",
@@ -10821,12 +12006,14 @@
"country",
"nation",
"aomen",
- "mo"
- ]
+ "mo",
+ "flag"
+ ],
+ "moji": "🇲🇴"
},
"flag_mp": {
"unicode": "1F1F2-1F1F5",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "northern mariana islands",
"shortname": ":flag_mp:",
"category": "flags",
@@ -10834,11 +12021,15 @@
":mp:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇲🇵"
},
"flag_mq": {
"unicode": "1F1F2-1F1F6",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "martinique",
"shortname": ":flag_mq:",
"category": "flags",
@@ -10846,7 +12037,11 @@
":mq:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇲🇶"
},
"flag_mr": {
"unicode": "1F1F2-1F1F7",
@@ -10862,8 +12057,10 @@
"country",
"nation",
"muritaniyah",
- "mr"
- ]
+ "mr",
+ "flag"
+ ],
+ "moji": "🇲🇷"
},
"flag_ms": {
"unicode": "1F1F2-1F1F8",
@@ -10878,8 +12075,10 @@
"keywords": [
"country",
"nation",
- "ms"
- ]
+ "ms",
+ "flag"
+ ],
+ "moji": "🇲🇸"
},
"flag_mt": {
"unicode": "1F1F2-1F1F9",
@@ -10894,8 +12093,10 @@
"keywords": [
"country",
"nation",
- "mt"
- ]
+ "mt",
+ "flag"
+ ],
+ "moji": "🇲🇹"
},
"flag_mu": {
"unicode": "1F1F2-1F1FA",
@@ -10910,8 +12111,10 @@
"keywords": [
"country",
"nation",
- "mu"
- ]
+ "mu",
+ "flag"
+ ],
+ "moji": "🇲🇺"
},
"flag_mv": {
"unicode": "1F1F2-1F1FB",
@@ -10927,8 +12130,10 @@
"country",
"nation",
"dhivehi raajje",
- "mv"
- ]
+ "mv",
+ "flag"
+ ],
+ "moji": "🇲🇻"
},
"flag_mw": {
"unicode": "1F1F2-1F1FC",
@@ -10943,8 +12148,10 @@
"keywords": [
"country",
"nation",
- "mw"
- ]
+ "mw",
+ "flag"
+ ],
+ "moji": "🇲🇼"
},
"flag_mx": {
"unicode": "1F1F2-1F1FD",
@@ -10959,8 +12166,11 @@
"keywords": [
"country",
"nation",
- "mx"
- ]
+ "mx",
+ "mexican",
+ "flag"
+ ],
+ "moji": "🇲🇽"
},
"flag_my": {
"unicode": "1F1F2-1F1FE",
@@ -10975,8 +12185,10 @@
"keywords": [
"country",
"nation",
- "my"
- ]
+ "my",
+ "flag"
+ ],
+ "moji": "🇲🇾"
},
"flag_mz": {
"unicode": "1F1F2-1F1FF",
@@ -10992,8 +12204,10 @@
"country",
"nation",
"mocambique",
- "mz"
- ]
+ "mz",
+ "flag"
+ ],
+ "moji": "🇲🇿"
},
"flag_na": {
"unicode": "1F1F3-1F1E6",
@@ -11008,8 +12222,10 @@
"keywords": [
"country",
"nation",
- "na"
- ]
+ "na",
+ "flag"
+ ],
+ "moji": "🇳🇦"
},
"flag_nc": {
"unicode": "1F1F3-1F1E8",
@@ -11027,8 +12243,10 @@
"nouvelle",
"cal&eacute;donie",
"caledonie",
- "nc"
- ]
+ "nc",
+ "flag"
+ ],
+ "moji": "🇳🇨"
},
"flag_ne": {
"unicode": "1F1F3-1F1EA",
@@ -11043,12 +12261,14 @@
"keywords": [
"country",
"nation",
- "ne"
- ]
+ "ne",
+ "flag"
+ ],
+ "moji": "🇳🇪"
},
"flag_nf": {
"unicode": "1F1F3-1F1EB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "norfolk island",
"shortname": ":flag_nf:",
"category": "flags",
@@ -11056,7 +12276,11 @@
":nf:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇳🇫"
},
"flag_ng": {
"unicode": "1F1F3-1F1EC",
@@ -11071,8 +12295,10 @@
"keywords": [
"country",
"nation",
- "ng"
- ]
+ "ng",
+ "flag"
+ ],
+ "moji": "🇳🇬"
},
"flag_ni": {
"unicode": "1F1F3-1F1EE",
@@ -11087,8 +12313,10 @@
"keywords": [
"country",
"nation",
- "ni"
- ]
+ "ni",
+ "flag"
+ ],
+ "moji": "🇳🇮"
},
"flag_nl": {
"unicode": "1F1F3-1F1F1",
@@ -11105,8 +12333,10 @@
"nation",
"nederland",
"holland",
- "nl"
- ]
+ "nl",
+ "flag"
+ ],
+ "moji": "🇳🇱"
},
"flag_no": {
"unicode": "1F1F3-1F1F4",
@@ -11122,8 +12352,10 @@
"country",
"nation",
"norge",
- "no"
- ]
+ "no",
+ "flag"
+ ],
+ "moji": "🇳🇴"
},
"flag_np": {
"unicode": "1F1F3-1F1F5",
@@ -11138,8 +12370,10 @@
"keywords": [
"country",
"nation",
- "np"
- ]
+ "np",
+ "flag"
+ ],
+ "moji": "🇳🇵"
},
"flag_nr": {
"unicode": "1F1F3-1F1F7",
@@ -11154,8 +12388,10 @@
"keywords": [
"country",
"nation",
- "nr"
- ]
+ "nr",
+ "flag"
+ ],
+ "moji": "🇳🇷"
},
"flag_nu": {
"unicode": "1F1F3-1F1FA",
@@ -11170,8 +12406,10 @@
"keywords": [
"country",
"nation",
- "nu"
- ]
+ "nu",
+ "flag"
+ ],
+ "moji": "🇳🇺"
},
"flag_nz": {
"unicode": "1F1F3-1F1FF",
@@ -11187,8 +12425,10 @@
"country",
"nation",
"aotearoa",
- "nz"
- ]
+ "nz",
+ "flag"
+ ],
+ "moji": "🇳🇿"
},
"flag_om": {
"unicode": "1F1F4-1F1F2",
@@ -11204,8 +12444,10 @@
"country",
"nation",
"saltanat uman",
- "om"
- ]
+ "om",
+ "flag"
+ ],
+ "moji": "🇴🇲"
},
"flag_pa": {
"unicode": "1F1F5-1F1E6",
@@ -11220,8 +12462,10 @@
"keywords": [
"country",
"nation",
- "pa"
- ]
+ "pa",
+ "flag"
+ ],
+ "moji": "🇵🇦"
},
"flag_pe": {
"unicode": "1F1F5-1F1EA",
@@ -11236,8 +12480,10 @@
"keywords": [
"country",
"nation",
- "pe"
- ]
+ "pe",
+ "flag"
+ ],
+ "moji": "🇵🇪"
},
"flag_pf": {
"unicode": "1F1F5-1F1EB",
@@ -11254,8 +12500,10 @@
"nation",
"polyn&eacute;sie fran&ccedil;aise",
"polynesie francaise",
- "pf"
- ]
+ "pf",
+ "flag"
+ ],
+ "moji": "🇵🇫"
},
"flag_pg": {
"unicode": "1F1F5-1F1EC",
@@ -11271,8 +12519,10 @@
"country",
"nation",
"papua niu gini",
- "pg"
- ]
+ "pg",
+ "flag"
+ ],
+ "moji": "🇵🇬"
},
"flag_ph": {
"unicode": "1F1F5-1F1ED",
@@ -11288,8 +12538,10 @@
"country",
"nation",
"pilipinas",
- "ph"
- ]
+ "ph",
+ "flag"
+ ],
+ "moji": "🇵🇭"
},
"flag_pk": {
"unicode": "1F1F5-1F1F0",
@@ -11304,8 +12556,10 @@
"keywords": [
"country",
"nation",
- "pk"
- ]
+ "pk",
+ "flag"
+ ],
+ "moji": "🇵🇰"
},
"flag_pl": {
"unicode": "1F1F5-1F1F1",
@@ -11321,12 +12575,14 @@
"country",
"nation",
"polska",
- "pl"
- ]
+ "pl",
+ "flag"
+ ],
+ "moji": "🇵🇱"
},
"flag_pm": {
"unicode": "1F1F5-1F1F2",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "saint pierre and miquelon",
"shortname": ":flag_pm:",
"category": "flags",
@@ -11334,11 +12590,15 @@
":pm:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇵🇲"
},
"flag_pn": {
"unicode": "1F1F5-1F1F3",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "pitcairn",
"shortname": ":flag_pn:",
"category": "flags",
@@ -11346,7 +12606,11 @@
":pn:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇵🇳"
},
"flag_pr": {
"unicode": "1F1F5-1F1F7",
@@ -11361,8 +12625,10 @@
"keywords": [
"country",
"nation",
- "pr"
- ]
+ "pr",
+ "flag"
+ ],
+ "moji": "🇵🇷"
},
"flag_ps": {
"unicode": "1F1F5-1F1F8",
@@ -11377,8 +12643,10 @@
"keywords": [
"country",
"nation",
- "ps"
- ]
+ "ps",
+ "flag"
+ ],
+ "moji": "🇵🇸"
},
"flag_pt": {
"unicode": "1F1F5-1F1F9",
@@ -11393,8 +12661,10 @@
"keywords": [
"country",
"nation",
- "pt"
- ]
+ "pt",
+ "flag"
+ ],
+ "moji": "🇵🇹"
},
"flag_pw": {
"unicode": "1F1F5-1F1FC",
@@ -11410,8 +12680,10 @@
"country",
"nation",
"belau",
- "pw"
- ]
+ "pw",
+ "flag"
+ ],
+ "moji": "🇵🇼"
},
"flag_py": {
"unicode": "1F1F5-1F1FE",
@@ -11426,8 +12698,10 @@
"keywords": [
"country",
"nation",
- "py"
- ]
+ "py",
+ "flag"
+ ],
+ "moji": "🇵🇾"
},
"flag_qa": {
"unicode": "1F1F6-1F1E6",
@@ -11443,12 +12717,14 @@
"country",
"nation",
"dawlat qatar",
- "qa"
- ]
+ "qa",
+ "flag"
+ ],
+ "moji": "🇶🇦"
},
"flag_re": {
"unicode": "1F1F7-1F1EA",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "réunion",
"shortname": ":flag_re:",
"category": "flags",
@@ -11456,7 +12732,11 @@
":re:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇷🇪"
},
"flag_ro": {
"unicode": "1F1F7-1F1F4",
@@ -11471,8 +12751,10 @@
"keywords": [
"country",
"nation",
- "ro"
- ]
+ "ro",
+ "flag"
+ ],
+ "moji": "🇷🇴"
},
"flag_rs": {
"unicode": "1F1F7-1F1F8",
@@ -11488,8 +12770,10 @@
"country",
"nation",
"srbija",
- "rs"
- ]
+ "rs",
+ "flag"
+ ],
+ "moji": "🇷🇸"
},
"flag_ru": {
"unicode": "1F1F7-1F1FA",
@@ -11505,8 +12789,10 @@
"nation",
"russian",
"country",
- "ru"
- ]
+ "ru",
+ "flag"
+ ],
+ "moji": "🇷🇺"
},
"flag_rw": {
"unicode": "1F1F7-1F1FC",
@@ -11521,8 +12807,10 @@
"keywords": [
"country",
"nation",
- "rw"
- ]
+ "rw",
+ "flag"
+ ],
+ "moji": "🇷🇼"
},
"flag_sa": {
"unicode": "1F1F8-1F1E6",
@@ -11539,8 +12827,10 @@
"country",
"nation",
"al arabiyah as suudiyah",
- "sa"
- ]
+ "sa",
+ "flag"
+ ],
+ "moji": "🇸🇦"
},
"flag_sb": {
"unicode": "1F1F8-1F1E7",
@@ -11555,8 +12845,10 @@
"keywords": [
"country",
"nation",
- "sb"
- ]
+ "sb",
+ "flag"
+ ],
+ "moji": "🇸🇧"
},
"flag_sc": {
"unicode": "1F1F8-1F1E8",
@@ -11572,8 +12864,10 @@
"country",
"nation",
"seychelles",
- "sc"
- ]
+ "sc",
+ "flag"
+ ],
+ "moji": "🇸🇨"
},
"flag_sd": {
"unicode": "1F1F8-1F1E9",
@@ -11589,8 +12883,10 @@
"country",
"nation",
"as-sudan",
- "sd"
- ]
+ "sd",
+ "flag"
+ ],
+ "moji": "🇸🇩"
},
"flag_se": {
"unicode": "1F1F8-1F1EA",
@@ -11606,8 +12902,10 @@
"country",
"nation",
"sverige",
- "se"
- ]
+ "se",
+ "flag"
+ ],
+ "moji": "🇸🇪"
},
"flag_sg": {
"unicode": "1F1F8-1F1EC",
@@ -11622,8 +12920,10 @@
"keywords": [
"country",
"nation",
- "sg"
- ]
+ "sg",
+ "flag"
+ ],
+ "moji": "🇸🇬"
},
"flag_sh": {
"unicode": "1F1F8-1F1ED",
@@ -11638,8 +12938,10 @@
"keywords": [
"country",
"nation",
- "sh"
- ]
+ "sh",
+ "flag"
+ ],
+ "moji": "🇸🇭"
},
"flag_si": {
"unicode": "1F1F8-1F1EE",
@@ -11655,12 +12957,14 @@
"country",
"nation",
"slovenija",
- "si"
- ]
+ "si",
+ "flag"
+ ],
+ "moji": "🇸🇮"
},
"flag_sj": {
"unicode": "1F1F8-1F1EF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "svalbard and jan mayen",
"shortname": ":flag_sj:",
"category": "flags",
@@ -11668,7 +12972,11 @@
":sj:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇸🇯"
},
"flag_sk": {
"unicode": "1F1F8-1F1F0",
@@ -11683,8 +12991,10 @@
"keywords": [
"country",
"nation",
- "sk"
- ]
+ "sk",
+ "flag"
+ ],
+ "moji": "🇸🇰"
},
"flag_sl": {
"unicode": "1F1F8-1F1F1",
@@ -11699,8 +13009,10 @@
"keywords": [
"country",
"nation",
- "sl"
- ]
+ "sl",
+ "flag"
+ ],
+ "moji": "🇸🇱"
},
"flag_sm": {
"unicode": "1F1F8-1F1F2",
@@ -11715,8 +13027,10 @@
"keywords": [
"country",
"nation",
- "sm"
- ]
+ "sm",
+ "flag"
+ ],
+ "moji": "🇸🇲"
},
"flag_sn": {
"unicode": "1F1F8-1F1F3",
@@ -11731,8 +13045,10 @@
"keywords": [
"country",
"nation",
- "sn"
- ]
+ "sn",
+ "flag"
+ ],
+ "moji": "🇸🇳"
},
"flag_so": {
"unicode": "1F1F8-1F1F4",
@@ -11747,8 +13063,10 @@
"keywords": [
"country",
"nation",
- "so"
- ]
+ "so",
+ "flag"
+ ],
+ "moji": "🇸🇴"
},
"flag_sr": {
"unicode": "1F1F8-1F1F7",
@@ -11763,12 +13081,14 @@
"keywords": [
"country",
"nation",
- "sr"
- ]
+ "sr",
+ "flag"
+ ],
+ "moji": "🇸🇷"
},
"flag_ss": {
"unicode": "1F1F8-1F1F8",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "south sudan",
"shortname": ":flag_ss:",
"category": "flags",
@@ -11776,7 +13096,11 @@
":ss:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇸🇸"
},
"flag_st": {
"unicode": "1F1F8-1F1F9",
@@ -11792,8 +13116,10 @@
"country",
"nation",
"sao tome e principe",
- "st"
- ]
+ "st",
+ "flag"
+ ],
+ "moji": "🇸🇹"
},
"flag_sv": {
"unicode": "1F1F8-1F1FB",
@@ -11808,12 +13134,14 @@
"keywords": [
"country",
"nation",
- "sv"
- ]
+ "sv",
+ "flag"
+ ],
+ "moji": "🇸🇻"
},
"flag_sx": {
"unicode": "1F1F8-1F1FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "sint maarten",
"shortname": ":flag_sx:",
"category": "flags",
@@ -11821,7 +13149,11 @@
":sx:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇸🇽"
},
"flag_sy": {
"unicode": "1F1F8-1F1FE",
@@ -11836,8 +13168,10 @@
"keywords": [
"country",
"nation",
- "sy"
- ]
+ "sy",
+ "flag"
+ ],
+ "moji": "🇸🇾"
},
"flag_sz": {
"unicode": "1F1F8-1F1FF",
@@ -11852,12 +13186,14 @@
"keywords": [
"country",
"nation",
- "sz"
- ]
+ "sz",
+ "flag"
+ ],
+ "moji": "🇸🇿"
},
"flag_ta": {
"unicode": "1F1F9-1F1E6",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "tristan da cunha",
"shortname": ":flag_ta:",
"category": "flags",
@@ -11865,11 +13201,15 @@
":ta:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇹🇦"
},
"flag_tc": {
"unicode": "1F1F9-1F1E8",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "turks and caicos islands",
"shortname": ":flag_tc:",
"category": "flags",
@@ -11877,7 +13217,11 @@
":tc:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇹🇨"
},
"flag_td": {
"unicode": "1F1F9-1F1E9",
@@ -11893,12 +13237,14 @@
"country",
"nation",
"tchad",
- "td"
- ]
+ "td",
+ "flag"
+ ],
+ "moji": "🇹🇩"
},
"flag_tf": {
"unicode": "1F1F9-1F1EB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "french southern territories",
"shortname": ":flag_tf:",
"category": "flags",
@@ -11906,7 +13252,11 @@
":tf:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇹🇫"
},
"flag_tg": {
"unicode": "1F1F9-1F1EC",
@@ -11922,8 +13272,10 @@
"country",
"nation",
"republique togolaise",
- "tg"
- ]
+ "tg",
+ "flag"
+ ],
+ "moji": "🇹🇬"
},
"flag_th": {
"unicode": "1F1F9-1F1ED",
@@ -11939,8 +13291,10 @@
"country",
"nation",
"prathet thai",
- "th"
- ]
+ "th",
+ "flag"
+ ],
+ "moji": "🇹🇭"
},
"flag_tj": {
"unicode": "1F1F9-1F1EF",
@@ -11956,12 +13310,14 @@
"country",
"nation",
"jumhurii tojikiston",
- "tj"
- ]
+ "tj",
+ "flag"
+ ],
+ "moji": "🇹🇯"
},
"flag_tk": {
"unicode": "1F1F9-1F1F0",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "tokelau",
"shortname": ":flag_tk:",
"category": "flags",
@@ -11969,7 +13325,11 @@
":tk:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇹🇰"
},
"flag_tl": {
"unicode": "1F1F9-1F1F1",
@@ -11984,8 +13344,10 @@
"keywords": [
"country",
"nation",
- "tl"
- ]
+ "tl",
+ "flag"
+ ],
+ "moji": "🇹🇱"
},
"flag_tm": {
"unicode": "1F1F9-1F1F2",
@@ -12000,8 +13362,10 @@
"keywords": [
"country",
"nation",
- "tm"
- ]
+ "tm",
+ "flag"
+ ],
+ "moji": "🇹🇲"
},
"flag_tn": {
"unicode": "1F1F9-1F1F3",
@@ -12017,8 +13381,10 @@
"country",
"nation",
"tunis",
- "tn"
- ]
+ "tn",
+ "flag"
+ ],
+ "moji": "🇹🇳"
},
"flag_to": {
"unicode": "1F1F9-1F1F4",
@@ -12033,8 +13399,10 @@
"keywords": [
"country",
"nation",
- "to"
- ]
+ "to",
+ "flag"
+ ],
+ "moji": "🇹🇴"
},
"flag_tr": {
"unicode": "1F1F9-1F1F7",
@@ -12049,8 +13417,10 @@
"keywords": [
"country",
"nation",
- "turkiye"
- ]
+ "turkiye",
+ "flag"
+ ],
+ "moji": "🇹🇷"
},
"flag_tt": {
"unicode": "1F1F9-1F1F9",
@@ -12065,8 +13435,10 @@
"keywords": [
"country",
"nation",
- "tt"
- ]
+ "tt",
+ "flag"
+ ],
+ "moji": "🇹🇹"
},
"flag_tv": {
"unicode": "1F1F9-1F1FB",
@@ -12081,8 +13453,10 @@
"keywords": [
"country",
"nation",
- "tv"
- ]
+ "tv",
+ "flag"
+ ],
+ "moji": "🇹🇻"
},
"flag_tw": {
"unicode": "1F1F9-1F1FC",
@@ -12098,8 +13472,10 @@
"country",
"nation",
"taiwan",
- "tw"
- ]
+ "tw",
+ "flag"
+ ],
+ "moji": "🇹🇼"
},
"flag_tz": {
"unicode": "1F1F9-1F1FF",
@@ -12114,8 +13490,10 @@
"keywords": [
"country",
"nation",
- "tz"
- ]
+ "tz",
+ "flag"
+ ],
+ "moji": "🇹🇿"
},
"flag_ua": {
"unicode": "1F1FA-1F1E6",
@@ -12131,8 +13509,10 @@
"country",
"nation",
"ukrayina",
- "ua"
- ]
+ "ua",
+ "flag"
+ ],
+ "moji": "🇺🇦"
},
"flag_ug": {
"unicode": "1F1FA-1F1EC",
@@ -12147,12 +13527,14 @@
"keywords": [
"country",
"nation",
- "ug"
- ]
+ "ug",
+ "flag"
+ ],
+ "moji": "🇺🇬"
},
"flag_um": {
"unicode": "1F1FA-1F1F2",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "united states minor outlying islands",
"shortname": ":flag_um:",
"category": "flags",
@@ -12160,7 +13542,11 @@
":um:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇺🇲"
},
"flag_us": {
"unicode": "1F1FA-1F1F8",
@@ -12180,8 +13566,10 @@
"united states of america",
"america",
"old glory",
- "us"
- ]
+ "us",
+ "flag"
+ ],
+ "moji": "🇺🇸"
},
"flag_uy": {
"unicode": "1F1FA-1F1FE",
@@ -12196,8 +13584,10 @@
"keywords": [
"country",
"nation",
- "uy"
- ]
+ "uy",
+ "flag"
+ ],
+ "moji": "🇺🇾"
},
"flag_uz": {
"unicode": "1F1FA-1F1FF",
@@ -12213,8 +13603,10 @@
"country",
"nation",
"uzbekiston respublikasi",
- "uz"
- ]
+ "uz",
+ "flag"
+ ],
+ "moji": "🇺🇿"
},
"flag_va": {
"unicode": "1F1FB-1F1E6",
@@ -12229,8 +13621,10 @@
"keywords": [
"country",
"nation",
- "va"
- ]
+ "va",
+ "flag"
+ ],
+ "moji": "🇻🇦"
},
"flag_vc": {
"unicode": "1F1FB-1F1E8",
@@ -12245,8 +13639,10 @@
"keywords": [
"country",
"nation",
- "vc"
- ]
+ "vc",
+ "flag"
+ ],
+ "moji": "🇻🇨"
},
"flag_ve": {
"unicode": "1F1FB-1F1EA",
@@ -12261,12 +13657,14 @@
"keywords": [
"country",
"nation",
- "ve"
- ]
+ "ve",
+ "flag"
+ ],
+ "moji": "🇻🇪"
},
"flag_vg": {
"unicode": "1F1FB-1F1EC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "british virgin islands",
"shortname": ":flag_vg:",
"category": "flags",
@@ -12274,7 +13672,11 @@
":vg:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇻🇬"
},
"flag_vi": {
"unicode": "1F1FB-1F1EE",
@@ -12289,8 +13691,10 @@
"keywords": [
"country",
"nation",
- "vi"
- ]
+ "vi",
+ "flag"
+ ],
+ "moji": "🇻🇮"
},
"flag_vn": {
"unicode": "1F1FB-1F1F3",
@@ -12306,8 +13710,10 @@
"country",
"nation",
"viet nam",
- "vn"
- ]
+ "vn",
+ "flag"
+ ],
+ "moji": "🇻🇳"
},
"flag_vu": {
"unicode": "1F1FB-1F1FA",
@@ -12322,8 +13728,10 @@
"keywords": [
"country",
"nation",
- "vu"
- ]
+ "vu",
+ "flag"
+ ],
+ "moji": "🇻🇺"
},
"flag_wf": {
"unicode": "1F1FC-1F1EB",
@@ -12338,23 +13746,27 @@
"keywords": [
"country",
"nation",
- "wf"
- ]
+ "wf",
+ "flag"
+ ],
+ "moji": "🇼🇫"
},
"flag_white": {
"unicode": "1F3F3",
"unicode_alternates": [],
"name": "waving white flag",
"shortname": ":flag_white:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [
":waving_white_flag:"
],
"aliases_ascii": [],
"keywords": [
"symbol",
- "signal"
- ]
+ "signal",
+ "object"
+ ],
+ "moji": "🏳"
},
"flag_ws": {
"unicode": "1F1FC-1F1F8",
@@ -12370,8 +13782,10 @@
"country",
"nation",
"american samoa",
- "ws"
- ]
+ "ws",
+ "flag"
+ ],
+ "moji": "🇼🇸"
},
"flag_xk": {
"unicode": "1F1FD-1F1F0",
@@ -12386,8 +13800,10 @@
"keywords": [
"country",
"nation",
- "xk"
- ]
+ "xk",
+ "flag"
+ ],
+ "moji": "🇽🇰"
},
"flag_ye": {
"unicode": "1F1FE-1F1EA",
@@ -12403,12 +13819,14 @@
"country",
"nation",
"al yaman",
- "ye"
- ]
+ "ye",
+ "flag"
+ ],
+ "moji": "🇾🇪"
},
"flag_yt": {
"unicode": "1F1FE-1F1F9",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "mayotte",
"shortname": ":flag_yt:",
"category": "flags",
@@ -12416,7 +13834,11 @@
":yt:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "country",
+ "flag"
+ ],
+ "moji": "🇾🇹"
},
"flag_za": {
"unicode": "1F1FF-1F1E6",
@@ -12430,8 +13852,10 @@
"aliases_ascii": [],
"keywords": [
"country",
- "nation"
- ]
+ "nation",
+ "flag"
+ ],
+ "moji": "🇿🇦"
},
"flag_zm": {
"unicode": "1F1FF-1F1F2",
@@ -12446,8 +13870,10 @@
"keywords": [
"country",
"nation",
- "zm"
- ]
+ "zm",
+ "flag"
+ ],
+ "moji": "🇿🇲"
},
"flag_zw": {
"unicode": "1F1FF-1F1FC",
@@ -12462,8 +13888,10 @@
"keywords": [
"country",
"nation",
- "zw"
- ]
+ "zw",
+ "flag"
+ ],
+ "moji": "🇿🇼"
},
"flags": {
"unicode": "1F38F",
@@ -12484,11 +13912,11 @@
"boys",
"celebration",
"happiness",
- "carp",
"streamers",
- "japanese",
"holiday",
- "flags"
+ "flags",
+ "object",
+ "japan"
],
"moji": "🎏"
},
@@ -12501,56 +13929,25 @@
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "dark"
+ "dark",
+ "electronics",
+ "object"
],
"moji": "🔦"
},
"fleur-de-lis": {
"unicode": "269C",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "fleur-de-lis",
"shortname": ":fleur-de-lis:",
"category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "symbol"
- ]
- },
- "flip_phone": {
- "unicode": "1F581",
- "unicode_alternates": [],
- "name": "clamshell mobile phone",
- "shortname": ":flip_phone:",
- "category": "objects_symbols",
- "aliases": [
- ":clamshell_mobile_phone:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "cellphone"
- ]
- },
- "floppy_black": {
- "unicode": "1F5AA",
- "unicode_alternates": [],
- "name": "black hard shell floppy disk",
- "shortname": ":floppy_black:",
- "category": "objects_symbols",
- "aliases": [
- ":black_hard_shell_floppy_disk:"
+ "symbol",
+ "object"
],
- "aliases_ascii": [],
- "keywords": [
- "oldschool",
- "save",
- "technology",
- "storage",
- "information",
- "computer",
- "drive",
- "megabyte"
- ]
+ "moji": "⚜"
},
"floppy_disk": {
"unicode": "1F4BE",
@@ -12570,37 +13967,18 @@
"information",
"computer",
"drive",
- "megabyte"
+ "megabyte",
+ "electronics",
+ "office"
],
"moji": "💾"
},
- "floppy_white": {
- "unicode": "1F5AB",
- "unicode_alternates": [],
- "name": "white hard shell floppy disk",
- "shortname": ":floppy_white:",
- "category": "objects_symbols",
- "aliases": [
- ":white_hard_shell_floppy_disk:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "oldschool",
- "save",
- "technology",
- "storage",
- "information",
- "computer",
- "drive",
- "megabyte"
- ]
- },
"flower_playing_cards": {
"unicode": "1F3B4",
"unicode_alternates": [],
"name": "flower playing cards",
"shortname": ":flower_playing_cards:",
- "category": "objects",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -12610,7 +13988,9 @@
"game",
"august",
"moon",
- "special"
+ "special",
+ "object",
+ "symbol"
],
"moji": "🎴"
},
@@ -12619,7 +13999,7 @@
"unicode_alternates": [],
"name": "flushed face",
"shortname": ":flushed:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [
":$",
@@ -12630,11 +14010,13 @@
"face",
"flattered",
"flush",
- "blush",
"red",
"pink",
"cheeks",
- "shy"
+ "shy",
+ "smiley",
+ "emotion",
+ "omg"
],
"moji": "😳"
},
@@ -12650,15 +14032,18 @@
"weather",
"damp",
"cloud",
- "hazy"
- ]
+ "hazy",
+ "sky",
+ "cold"
+ ],
+ "moji": "🌫"
},
"foggy": {
"unicode": "1F301",
"unicode_alternates": [],
"name": "foggy",
"shortname": ":foggy:",
- "category": "nature",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -12667,43 +14052,21 @@
"bridge",
"weather",
"fog",
- "foggy"
+ "foggy",
+ "places",
+ "building",
+ "sky",
+ "travel",
+ "vacation"
],
"moji": "🌁"
},
- "folder": {
- "unicode": "1F5C0",
- "unicode_alternates": [],
- "name": "folder",
- "shortname": ":folder:",
- "category": "objects_symbols",
- "aliases": [],
- "aliases_ascii": [],
- "keywords": [
- "documents"
- ]
- },
- "folder_open": {
- "unicode": "1F5C1",
- "unicode_alternates": [],
- "name": "open folder",
- "shortname": ":folder_open:",
- "category": "objects_symbols",
- "aliases": [
- ":open_folder:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "documents",
- "load"
- ]
- },
"football": {
"unicode": "1F3C8",
"unicode_alternates": [],
"name": "american football",
"shortname": ":football:",
- "category": "objects",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -12714,7 +14077,8 @@
"ball",
"sport",
"america",
- "american"
+ "american",
+ "game"
],
"moji": "🏈"
},
@@ -12723,7 +14087,7 @@
"unicode_alternates": [],
"name": "footprints",
"shortname": ":footprints:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -12736,7 +14100,7 @@
"unicode_alternates": [],
"name": "fork and knife",
"shortname": ":fork_and_knife:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -12747,7 +14111,9 @@
"restaurant",
"meal",
"food",
- "eat"
+ "eat",
+ "object",
+ "weapon"
],
"moji": "🍴"
},
@@ -12756,7 +14122,7 @@
"unicode_alternates": [],
"name": "fork and knife with plate",
"shortname": ":fork_knife_plate:",
- "category": "travel_places",
+ "category": "food",
"aliases": [
":fork_and_knife_with_plate:"
],
@@ -12768,8 +14134,10 @@
"lunch",
"dinner",
"utensils",
- "setting"
- ]
+ "setting",
+ "object"
+ ],
+ "moji": "🍽"
},
"fountain": {
"unicode": "26F2",
@@ -12778,11 +14146,13 @@
],
"name": "fountain",
"shortname": ":fountain:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "photo"
+ "photo",
+ "travel",
+ "vacation"
],
"moji": "⛲"
},
@@ -12792,15 +14162,18 @@
"unicode_alternates": [
"0034-FE0F-20E3"
],
- "name": "digit four",
+ "name": "keycap digit four",
"shortname": ":four:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"4",
"blue-square",
- "numbers"
+ "numbers",
+ "number",
+ "math",
+ "symbol"
]
},
"four_leaf_clover": {
@@ -12823,74 +14196,75 @@
"irish",
"saint",
"patrick",
- "green"
+ "green",
+ "sol"
],
"moji": "🍀"
},
- "frame_photo": {
- "unicode": "1F5BC",
+ "fox": {
+ "unicode": "1F98A",
"unicode_alternates": [],
- "name": "frame with picture",
- "shortname": ":frame_photo:",
- "category": "objects_symbols",
+ "name": "fox face",
+ "shortname": ":fox:",
+ "category": "nature",
"aliases": [
- ":frame_with_picture:"
+ ":fox_face:"
],
"aliases_ascii": [],
- "keywords": [
- "photo"
- ]
+ "keywords": [],
+ "moji": "🦊"
},
- "frame_tiles": {
- "unicode": "1F5BD",
+ "frame_photo": {
+ "unicode": "1F5BC",
"unicode_alternates": [],
- "name": "frame with tiles",
- "shortname": ":frame_tiles:",
- "category": "objects_symbols",
+ "name": "frame with picture",
+ "shortname": ":frame_photo:",
+ "category": "objects",
"aliases": [
- ":frame_with_tiles:"
+ ":frame_with_picture:"
],
"aliases_ascii": [],
"keywords": [
"photo",
- "painting"
- ]
- },
- "frame_x": {
- "unicode": "1F5BE",
- "unicode_alternates": [],
- "name": "frame with an x",
- "shortname": ":frame_x:",
- "category": "objects_symbols",
- "aliases": [
- ":frame_with_an_x:"
+ "travel",
+ "vacation"
],
- "aliases_ascii": [],
- "keywords": [
- "photo",
- "painting"
- ]
+ "moji": "🖼"
},
"free": {
"unicode": "1F193",
"unicode_alternates": [],
"name": "squared free",
"shortname": ":free:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"blue-square",
- "words"
+ "words",
+ "symbol"
],
"moji": "🆓"
},
+ "french_bread": {
+ "unicode": "1F956",
+ "unicode_alternates": [],
+ "name": "baguette bread",
+ "shortname": ":french_bread:",
+ "category": "food",
+ "aliases": [
+ ":baguette_bread:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🥖"
+ },
"fried_shrimp": {
"unicode": "1F364",
"unicode_alternates": [],
"name": "fried shrimp",
"shortname": ":fried_shrimp:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -12909,7 +14283,7 @@
"unicode_alternates": [],
"name": "french fries",
"shortname": ":fries:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -12920,7 +14294,8 @@
"potato",
"fry",
"russet",
- "idaho"
+ "idaho",
+ "america"
],
"moji": "🍟"
},
@@ -12934,7 +14309,8 @@
"aliases_ascii": [],
"keywords": [
"animal",
- "nature"
+ "nature",
+ "wildlife"
],
"moji": "🐸"
},
@@ -12943,7 +14319,7 @@
"unicode_alternates": [],
"name": "frowning face with open mouth",
"shortname": ":frowning:",
- "category": "emoticons",
+ "category": "people",
"aliases": [
":anguished:"
],
@@ -12955,13 +14331,16 @@
"sad",
"pout",
"sulk",
- "glower"
+ "glower",
+ "smiley",
+ "surprised",
+ "emotion"
],
"moji": "😦"
},
"frowning2": {
"unicode": "2639",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "white frowning face",
"shortname": ":frowning2:",
"category": "people",
@@ -12971,8 +14350,12 @@
"aliases_ascii": [],
"keywords": [
"frown",
- "person"
- ]
+ "person",
+ "sad",
+ "smiley",
+ "emotion"
+ ],
+ "moji": "☹"
},
"fuelpump": {
"unicode": "26FD",
@@ -12981,12 +14364,14 @@
],
"name": "fuel pump",
"shortname": ":fuelpump:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"gas station",
- "petroleum"
+ "petroleum",
+ "object",
+ "gas pump"
],
"moji": "⛽"
},
@@ -13010,7 +14395,8 @@
"monster",
"spooky",
"werewolves",
- "twilight"
+ "twilight",
+ "space"
],
"moji": "🌕"
},
@@ -13029,12 +14415,13 @@
"anthropomorphic",
"face",
"sky",
- "night",
"cheese",
"phase",
"spooky",
"werewolves",
- "monsters"
+ "monsters",
+ "space",
+ "goodnight"
],
"moji": "🌝"
},
@@ -13043,23 +14430,24 @@
"unicode_alternates": [],
"name": "game die",
"shortname": ":game_die:",
- "category": "objects",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"dice",
"game",
"die",
- "dice",
"craps",
"gamble",
- "play"
+ "play",
+ "object",
+ "boys night"
],
"moji": "🎲"
},
"gear": {
"unicode": "2699",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "gear",
"shortname": ":gear:",
"category": "objects",
@@ -13068,19 +14456,22 @@
"keywords": [
"object",
"tool"
- ]
+ ],
+ "moji": "⚙"
},
"gem": {
"unicode": "1F48E",
"unicode_alternates": [],
"name": "gem stone",
"shortname": ":gem:",
- "category": "emoticons",
+ "category": "objects",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"blue",
- "ruby"
+ "ruby",
+ "object",
+ "gem"
],
"moji": "💎"
},
@@ -13091,7 +14482,7 @@
],
"name": "gemini",
"shortname": ":gemini:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -13103,9 +14494,8 @@
"stars",
"zodiac",
"sign",
- "sign",
- "zodiac",
- "horoscope"
+ "horoscope",
+ "symbol"
],
"moji": "♊"
},
@@ -13114,11 +14504,13 @@
"unicode_alternates": [],
"name": "ghost",
"shortname": ":ghost:",
- "category": "objects",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "halloween"
+ "halloween",
+ "holidays",
+ "monster"
],
"moji": "👻"
},
@@ -13136,11 +14528,12 @@
"present",
"xmas",
"gift",
- "present",
"wrap",
"package",
- "birthday",
- "wedding"
+ "wedding",
+ "object",
+ "holidays",
+ "parties"
],
"moji": "🎁"
},
@@ -13149,12 +14542,14 @@
"unicode_alternates": [],
"name": "heart with ribbon",
"shortname": ":gift_heart:",
- "category": "objects",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"love",
- "valentines"
+ "valentines",
+ "symbol",
+ "condolence"
],
"moji": "💝"
},
@@ -13163,18 +14558,22 @@
"unicode_alternates": [],
"name": "girl",
"shortname": ":girl:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"female",
- "woman"
+ "woman",
+ "people",
+ "women",
+ "baby",
+ "diversity"
],
"moji": "👧"
},
"girl_tone1": {
"unicode": "1F467-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "girl tone 1",
"shortname": ":girl_tone1:",
"category": "people",
@@ -13184,11 +14583,12 @@
"female",
"kid",
"child"
- ]
+ ],
+ "moji": "👧🏻"
},
"girl_tone2": {
"unicode": "1F467-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "girl tone 2",
"shortname": ":girl_tone2:",
"category": "people",
@@ -13198,11 +14598,12 @@
"female",
"kid",
"child"
- ]
+ ],
+ "moji": "👧🏼"
},
"girl_tone3": {
"unicode": "1F467-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "girl tone 3",
"shortname": ":girl_tone3:",
"category": "people",
@@ -13212,11 +14613,12 @@
"female",
"kid",
"child"
- ]
+ ],
+ "moji": "👧🏽"
},
"girl_tone4": {
"unicode": "1F467-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "girl tone 4",
"shortname": ":girl_tone4:",
"category": "people",
@@ -13226,11 +14628,12 @@
"female",
"kid",
"child"
- ]
+ ],
+ "moji": "👧🏾"
},
"girl_tone5": {
"unicode": "1F467-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "girl tone 5",
"shortname": ":girl_tone5:",
"category": "people",
@@ -13240,42 +14643,43 @@
"female",
"kid",
"child"
- ]
- },
- "girls_symbol": {
- "unicode": "1F6CA",
- "unicode_alternates": [],
- "name": "girls symbol",
- "shortname": ":girls_symbol:",
- "category": "objects_symbols",
- "aliases": [],
- "aliases_ascii": [],
- "keywords": [
- "female",
- "child"
- ]
+ ],
+ "moji": "👧🏿"
},
"globe_with_meridians": {
"unicode": "1F310",
"unicode_alternates": [],
"name": "globe with meridians",
"shortname": ":globe_with_meridians:",
- "category": "nature",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"earth",
"international",
"world",
- "earth",
"meridian",
"globe",
"space",
"planet",
- "home"
+ "home",
+ "symbol"
],
"moji": "🌐"
},
+ "goal": {
+ "unicode": "1F945",
+ "unicode_alternates": [],
+ "name": "goal net",
+ "shortname": ":goal:",
+ "category": "activity",
+ "aliases": [
+ ":goal_net:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🥅"
+ },
"goat": {
"unicode": "1F410",
"unicode_alternates": [],
@@ -13302,12 +14706,17 @@
],
"name": "flag in hole",
"shortname": ":golf:",
- "category": "objects",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"business",
- "sports"
+ "sports",
+ "game",
+ "ball",
+ "vacation",
+ "sport",
+ "golf"
],
"moji": "⛳"
},
@@ -13324,15 +14733,32 @@
"par",
"birdie",
"eagle",
- "mulligan"
- ]
+ "mulligan",
+ "men",
+ "game",
+ "ball",
+ "vacation",
+ "golf"
+ ],
+ "moji": "🏌"
+ },
+ "gorilla": {
+ "unicode": "1F98D",
+ "unicode_alternates": [],
+ "name": "gorilla",
+ "shortname": ":gorilla:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🦍"
},
"grapes": {
"unicode": "1F347",
"unicode_alternates": [],
"name": "grapes",
"shortname": ":grapes:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -13341,7 +14767,6 @@
"grapes",
"wine",
"vinegar",
- "fruit",
"cluster",
"vine"
],
@@ -13352,19 +14777,19 @@
"unicode_alternates": [],
"name": "green apple",
"shortname": ":green_apple:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"fruit",
"nature",
"apple",
- "fruit",
"green",
"pie",
"granny",
"smith",
- "core"
+ "core",
+ "food"
],
"moji": "🍏"
},
@@ -13379,7 +14804,10 @@
"keywords": [
"knowledge",
"library",
- "read"
+ "read",
+ "object",
+ "office",
+ "book"
],
"moji": "📗"
},
@@ -13388,7 +14816,7 @@
"unicode_alternates": [],
"name": "green heart",
"shortname": ":green_heart:",
- "category": "emoticons",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -13398,14 +14826,14 @@
"valentines",
"green",
"heart",
- "love",
"nature",
"rebirth",
"reborn",
"jealous",
"clingy",
"envious",
- "possessive"
+ "possessive",
+ "symbol"
],
"moji": "💚"
},
@@ -13414,11 +14842,13 @@
"unicode_alternates": [],
"name": "white exclamation mark ornament",
"shortname": ":grey_exclamation:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "surprise"
+ "surprise",
+ "symbol",
+ "punctuation"
],
"moji": "❕"
},
@@ -13427,11 +14857,13 @@
"unicode_alternates": [],
"name": "white question mark ornament",
"shortname": ":grey_question:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "doubts"
+ "doubts",
+ "symbol",
+ "punctuation"
],
"moji": "❔"
},
@@ -13440,16 +14872,19 @@
"unicode_alternates": [],
"name": "grimacing face",
"shortname": ":grimacing:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"face",
"grimace",
"teeth",
- "grimace",
"disapprove",
- "pain"
+ "pain",
+ "silly",
+ "smiley",
+ "emotion",
+ "selfie"
],
"moji": "😬"
},
@@ -13458,7 +14893,7 @@
"unicode_alternates": [],
"name": "grinning face with smiling eyes",
"shortname": ":grin:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -13469,8 +14904,11 @@
"grin",
"grinning",
"smiling",
- "smile",
- "smiley"
+ "smiley",
+ "silly",
+ "emotion",
+ "good",
+ "selfie"
],
"moji": "😁"
},
@@ -13479,7 +14917,7 @@
"unicode_alternates": [],
"name": "grinning face",
"shortname": ":grinning:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -13490,17 +14928,17 @@
"grin",
"grinning",
"smiling",
- "smile",
- "smiley"
+ "smiley",
+ "emotion"
],
- "moji": "🕧"
+ "moji": "😀"
},
"guardsman": {
"unicode": "1F482",
"unicode_alternates": [],
"name": "guardsman",
"shortname": ":guardsman:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -13513,16 +14951,19 @@
"guard",
"bearskin",
"hat",
- "british",
"queen",
"ceremonial",
- "military"
+ "military",
+ "people",
+ "men",
+ "diversity",
+ "job"
],
"moji": "💂"
},
"guardsman_tone1": {
"unicode": "1F482-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "guardsman tone 1",
"shortname": ":guardsman_tone1:",
"category": "people",
@@ -13537,15 +14978,15 @@
"guard",
"bearskin",
"hat",
- "british",
"queen",
"ceremonial",
"military"
- ]
+ ],
+ "moji": "💂🏻"
},
"guardsman_tone2": {
"unicode": "1F482-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "guardsman tone 2",
"shortname": ":guardsman_tone2:",
"category": "people",
@@ -13560,15 +15001,15 @@
"guard",
"bearskin",
"hat",
- "british",
"queen",
"ceremonial",
"military"
- ]
+ ],
+ "moji": "💂🏼"
},
"guardsman_tone3": {
"unicode": "1F482-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "guardsman tone 3",
"shortname": ":guardsman_tone3:",
"category": "people",
@@ -13583,15 +15024,15 @@
"guard",
"bearskin",
"hat",
- "british",
"queen",
"ceremonial",
"military"
- ]
+ ],
+ "moji": "💂🏽"
},
"guardsman_tone4": {
"unicode": "1F482-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "guardsman tone 4",
"shortname": ":guardsman_tone4:",
"category": "people",
@@ -13606,15 +15047,15 @@
"guard",
"bearskin",
"hat",
- "british",
"queen",
"ceremonial",
"military"
- ]
+ ],
+ "moji": "💂🏾"
},
"guardsman_tone5": {
"unicode": "1F482-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "guardsman tone 5",
"shortname": ":guardsman_tone5:",
"category": "people",
@@ -13629,18 +15070,18 @@
"guard",
"bearskin",
"hat",
- "british",
"queen",
"ceremonial",
"military"
- ]
+ ],
+ "moji": "💂🏿"
},
"guitar": {
"unicode": "1F3B8",
"unicode_alternates": [],
"name": "guitar",
"shortname": ":guitar:",
- "category": "objects",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -13648,12 +15089,11 @@
"music",
"guitar",
"string",
- "music",
- "instrument",
"jam",
"rock",
"acoustic",
- "electric"
+ "electric",
+ "instruments"
],
"moji": "🎸"
},
@@ -13667,7 +15107,11 @@
"aliases_ascii": [],
"keywords": [
"violence",
- "weapon"
+ "weapon",
+ "object",
+ "dead",
+ "gun",
+ "sarcastic"
],
"moji": "🔫"
},
@@ -13676,19 +15120,22 @@
"unicode_alternates": [],
"name": "haircut",
"shortname": ":haircut:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"female",
"girl",
- "woman"
+ "woman",
+ "people",
+ "women",
+ "diversity"
],
"moji": "💇"
},
"haircut_tone1": {
"unicode": "1F487-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "haircut tone 1",
"shortname": ":haircut_tone1:",
"category": "people",
@@ -13698,11 +15145,12 @@
"female",
"girl",
"woman"
- ]
+ ],
+ "moji": "💇🏻"
},
"haircut_tone2": {
"unicode": "1F487-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "haircut tone 2",
"shortname": ":haircut_tone2:",
"category": "people",
@@ -13712,11 +15160,12 @@
"female",
"girl",
"woman"
- ]
+ ],
+ "moji": "💇🏼"
},
"haircut_tone3": {
"unicode": "1F487-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "haircut tone 3",
"shortname": ":haircut_tone3:",
"category": "people",
@@ -13726,11 +15175,12 @@
"female",
"girl",
"woman"
- ]
+ ],
+ "moji": "💇🏽"
},
"haircut_tone4": {
"unicode": "1F487-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "haircut tone 4",
"shortname": ":haircut_tone4:",
"category": "people",
@@ -13740,11 +15190,12 @@
"female",
"girl",
"woman"
- ]
+ ],
+ "moji": "💇🏾"
},
"haircut_tone5": {
"unicode": "1F487-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "haircut tone 5",
"shortname": ":haircut_tone5:",
"category": "people",
@@ -13754,14 +15205,15 @@
"female",
"girl",
"woman"
- ]
+ ],
+ "moji": "💇🏿"
},
"hamburger": {
"unicode": "1F354",
"unicode_alternates": [],
"name": "hamburger",
"shortname": ":hamburger:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -13769,9 +15221,9 @@
"meat",
"hamburger",
"burger",
- "meat",
"cow",
- "beef"
+ "beef",
+ "america"
],
"moji": "🍔"
},
@@ -13789,13 +15241,16 @@
"law",
"ruling",
"tools",
- "verdict"
+ "verdict",
+ "object",
+ "tool",
+ "weapon"
],
"moji": "🔨"
},
"hammer_pick": {
"unicode": "2692",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "hammer and pick",
"shortname": ":hammer_pick:",
"category": "objects",
@@ -13805,8 +15260,10 @@
"aliases_ascii": [],
"keywords": [
"object",
- "tool"
- ]
+ "tool",
+ "weapon"
+ ],
+ "moji": "⚒"
},
"hamster": {
"unicode": "1F439",
@@ -13836,29 +15293,16 @@
"hi",
"five",
"stop",
- "halt"
- ]
- },
- "hand_splayed_reverse": {
- "unicode": "1F591",
- "unicode_alternates": [],
- "name": "reversed raised hand with fingers splayed",
- "shortname": ":hand_splayed_reverse:",
- "category": "people",
- "aliases": [
- ":reversed_raised_hand_with_fingers_splayed:"
+ "halt",
+ "body",
+ "hands",
+ "diversity"
],
- "aliases_ascii": [],
- "keywords": [
- "hi",
- "five",
- "stop",
- "halt"
- ]
+ "moji": "🖐"
},
"hand_splayed_tone1": {
"unicode": "1F590-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "raised hand with fingers splayed tone 1",
"shortname": ":hand_splayed_tone1:",
"category": "people",
@@ -13871,11 +15315,12 @@
"five",
"stop",
"halt"
- ]
+ ],
+ "moji": "🖐🏻"
},
"hand_splayed_tone2": {
"unicode": "1F590-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "raised hand with fingers splayed tone 2",
"shortname": ":hand_splayed_tone2:",
"category": "people",
@@ -13888,11 +15333,12 @@
"five",
"stop",
"halt"
- ]
+ ],
+ "moji": "🖐🏼"
},
"hand_splayed_tone3": {
"unicode": "1F590-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "raised hand with fingers splayed tone 3",
"shortname": ":hand_splayed_tone3:",
"category": "people",
@@ -13905,11 +15351,12 @@
"five",
"stop",
"halt"
- ]
+ ],
+ "moji": "🖐🏽"
},
"hand_splayed_tone4": {
"unicode": "1F590-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "raised hand with fingers splayed tone 4",
"shortname": ":hand_splayed_tone4:",
"category": "people",
@@ -13922,11 +15369,12 @@
"five",
"stop",
"halt"
- ]
+ ],
+ "moji": "🖐🏾"
},
"hand_splayed_tone5": {
"unicode": "1F590-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "raised hand with fingers splayed tone 5",
"shortname": ":hand_splayed_tone5:",
"category": "people",
@@ -13939,57 +15387,170 @@
"five",
"stop",
"halt"
- ]
- },
- "hand_victory": {
- "unicode": "1F594",
- "unicode_alternates": [],
- "name": "reversed victory hand",
- "shortname": ":hand_victory:",
- "category": "people",
- "aliases": [
- ":reversed_victory_hand:"
],
- "aliases_ascii": [],
- "keywords": [
- "fu"
- ]
+ "moji": "🖐🏿"
},
"handbag": {
"unicode": "1F45C",
"unicode_alternates": [],
"name": "handbag",
"shortname": ":handbag:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"accessories",
"accessory",
"bag",
- "fashion"
+ "fashion",
+ "women",
+ "vacation"
],
"moji": "👜"
},
- "hard_disk": {
- "unicode": "1F5B4",
+ "handball": {
+ "unicode": "1F93E",
"unicode_alternates": [],
- "name": "hard disk",
- "shortname": ":hard_disk:",
- "category": "objects_symbols",
+ "name": "handball",
+ "shortname": ":handball:",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
- "keywords": [
- "save",
- "technology",
- "storage",
- "information",
- "computer",
- "drive",
- "megabyte",
- "gigabyte",
- "hd"
- ]
+ "keywords": [],
+ "moji": "🤾"
+ },
+ "handball_tone1": {
+ "unicode": "1F93E-1F3FB",
+ "unicode_alternates": [],
+ "name": "handball tone 1",
+ "shortname": ":handball_tone1:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤾🏻"
+ },
+ "handball_tone2": {
+ "unicode": "1F93E-1F3FC",
+ "unicode_alternates": [],
+ "name": "handball tone 2",
+ "shortname": ":handball_tone2:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤾🏼"
+ },
+ "handball_tone3": {
+ "unicode": "1F93E-1F3FD",
+ "unicode_alternates": [],
+ "name": "handball tone 3",
+ "shortname": ":handball_tone3:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤾🏽"
+ },
+ "handball_tone4": {
+ "unicode": "1F93E-1F3FE",
+ "unicode_alternates": [],
+ "name": "handball tone 4",
+ "shortname": ":handball_tone4:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤾🏾"
+ },
+ "handball_tone5": {
+ "unicode": "1F93E-1F3FF",
+ "unicode_alternates": [],
+ "name": "handball tone 5",
+ "shortname": ":handball_tone5:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤾🏿"
+ },
+ "handshake": {
+ "unicode": "1F91D",
+ "unicode_alternates": [],
+ "name": "handshake",
+ "shortname": ":handshake:",
+ "category": "people",
+ "aliases": [
+ ":shaking_hands:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤝"
+ },
+ "handshake_tone1": {
+ "unicode": "1F91D-1F3FB",
+ "unicode_alternates": [],
+ "name": "handshake tone 1",
+ "shortname": ":handshake_tone1:",
+ "category": "people",
+ "aliases": [
+ ":shaking_hands_tone1:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤝🏻"
+ },
+ "handshake_tone2": {
+ "unicode": "1F91D-1F3FC",
+ "unicode_alternates": [],
+ "name": "handshake tone 2",
+ "shortname": ":handshake_tone2:",
+ "category": "people",
+ "aliases": [
+ ":shaking_hands_tone2:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤝🏼"
+ },
+ "handshake_tone3": {
+ "unicode": "1F91D-1F3FD",
+ "unicode_alternates": [],
+ "name": "handshake tone 3",
+ "shortname": ":handshake_tone3:",
+ "category": "people",
+ "aliases": [
+ ":shaking_hands_tone3:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤝🏽"
+ },
+ "handshake_tone4": {
+ "unicode": "1F91D-1F3FE",
+ "unicode_alternates": [],
+ "name": "handshake tone 4",
+ "shortname": ":handshake_tone4:",
+ "category": "people",
+ "aliases": [
+ ":shaking_hands_tone4:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤝🏾"
+ },
+ "handshake_tone5": {
+ "unicode": "1F91D-1F3FF",
+ "unicode_alternates": [],
+ "name": "handshake tone 5",
+ "shortname": ":handshake_tone5:",
+ "category": "people",
+ "aliases": [
+ ":shaking_hands_tone5:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤝🏿"
},
"hash": {
"moji": "#⃣",
@@ -13999,11 +15560,12 @@
],
"name": "number sign",
"shortname": ":hash:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "symbol"
+ "symbol",
+ "number"
]
},
"hatched_chick": {
@@ -14018,12 +15580,11 @@
"baby",
"chicken",
"chick",
- "baby",
"bird",
- "chicken",
"young",
"woman",
- "cute"
+ "cute",
+ "animal"
],
"moji": "🐥"
},
@@ -14040,19 +15601,18 @@
"chicken",
"egg",
"chick",
- "egg",
"baby",
"bird",
- "chicken",
"young",
"woman",
- "cute"
+ "cute",
+ "animal"
],
"moji": "🐣"
},
"head_bandage": {
"unicode": "1F915",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "face with head-bandage",
"shortname": ":head_bandage:",
"category": "people",
@@ -14060,14 +15620,20 @@
":face_with_head_bandage:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "smiley",
+ "health",
+ "sick",
+ "emotion"
+ ],
+ "moji": "🤕"
},
"headphones": {
"unicode": "1F3A7",
"unicode_alternates": [],
"name": "headphone",
"shortname": ":headphones:",
- "category": "objects",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -14076,12 +15642,12 @@
"score",
"headphone",
"sound",
- "music",
"ears",
"beats",
"buds",
"audio",
- "listen"
+ "listen",
+ "instruments"
],
"moji": "🎧"
},
@@ -14090,13 +15656,12 @@
"unicode_alternates": [],
"name": "hear-no-evil monkey",
"shortname": ":hear_no_evil:",
- "category": "emoticons",
+ "category": "nature",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"animal",
"monkey",
- "monkey",
"ears",
"hear",
"sound",
@@ -14112,7 +15677,7 @@
],
"name": "heavy black heart",
"shortname": ":heart:",
- "category": "emoticons",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [
"<3"
@@ -14124,7 +15689,6 @@
"pink",
"black",
"heart",
- "love",
"passion",
"romance",
"intense",
@@ -14132,7 +15696,9 @@
"death",
"evil",
"cold",
- "valentines"
+ "valentines",
+ "symbol",
+ "parties"
]
},
"heart_decoration": {
@@ -14140,19 +15706,20 @@
"unicode_alternates": [],
"name": "heart decoration",
"shortname": ":heart_decoration:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"like",
"love",
- "purple-square"
+ "purple-square",
+ "symbol"
],
"moji": "💟"
},
"heart_exclamation": {
"unicode": "2763",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "heavy heart exclamation mark ornament",
"shortname": ":heart_exclamation:",
"category": "symbols",
@@ -14163,15 +15730,17 @@
"keywords": [
"emotion",
"punctuation",
- "symbol"
- ]
+ "symbol",
+ "love"
+ ],
+ "moji": "❣"
},
"heart_eyes": {
"unicode": "1F60D",
"unicode_alternates": [],
"name": "smiling face with heart-shaped eyes",
"shortname": ":heart_eyes:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -14185,10 +15754,15 @@
"smiling",
"heart",
"lovestruck",
- "love",
"flirt",
"smile",
- "heart-shaped"
+ "heart-shaped",
+ "happy",
+ "smiley",
+ "sex",
+ "heart eyes",
+ "emotion",
+ "beautiful"
],
"moji": "😍"
},
@@ -14197,7 +15771,7 @@
"unicode_alternates": [],
"name": "smiling cat face with heart-shaped eyes",
"shortname": ":heart_eyes_cat:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -14208,41 +15782,27 @@
"love",
"valentines",
"lovestruck",
- "love",
- "heart"
+ "heart",
+ "heart eyes",
+ "cat",
+ "beautiful"
],
"moji": "😻"
},
- "heart_tip": {
- "unicode": "1F394",
- "unicode_alternates": [],
- "name": "heart with tip on the left",
- "shortname": ":heart_tip:",
- "category": "celebration",
- "aliases": [
- ":heart_with_tip_on_the_left:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "affection",
- "like",
- "love",
- "valentines"
- ]
- },
"heartbeat": {
"unicode": "1F493",
"unicode_alternates": [],
"name": "beating heart",
"shortname": ":heartbeat:",
- "category": "emoticons",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"affection",
"like",
"love",
- "valentines"
+ "valentines",
+ "symbol"
],
"moji": "💓"
},
@@ -14251,14 +15811,15 @@
"unicode_alternates": [],
"name": "growing heart",
"shortname": ":heartpulse:",
- "category": "emoticons",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"affection",
"like",
"love",
- "valentines"
+ "valentines",
+ "symbol"
],
"moji": "💗"
},
@@ -14269,12 +15830,15 @@
],
"name": "black heart suit",
"shortname": ":hearts:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"cards",
- "poker"
+ "poker",
+ "love",
+ "symbol",
+ "game"
],
"moji": "♥"
},
@@ -14285,12 +15849,13 @@
],
"name": "heavy check mark",
"shortname": ":heavy_check_mark:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"nike",
- "ok"
+ "ok",
+ "symbol"
],
"moji": "✔"
},
@@ -14299,13 +15864,14 @@
"unicode_alternates": [],
"name": "heavy division sign",
"shortname": ":heavy_division_sign:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"calculation",
"divide",
- "math"
+ "math",
+ "symbol"
],
"moji": "➗"
},
@@ -14314,7 +15880,7 @@
"unicode_alternates": [],
"name": "heavy dollar sign",
"shortname": ":heavy_dollar_sign:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -14322,12 +15888,12 @@
"money",
"payment",
"dollar",
- "currency",
- "money",
"cash",
"sale",
"purchase",
- "value"
+ "value",
+ "math",
+ "symbol"
],
"moji": "💲"
},
@@ -14336,12 +15902,13 @@
"unicode_alternates": [],
"name": "heavy minus sign",
"shortname": ":heavy_minus_sign:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"calculation",
- "math"
+ "math",
+ "symbol"
],
"moji": "➖"
},
@@ -14352,12 +15919,13 @@
],
"name": "heavy multiplication x",
"shortname": ":heavy_multiplication_x:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"calculation",
- "math"
+ "math",
+ "symbol"
],
"moji": "✖"
},
@@ -14366,12 +15934,13 @@
"unicode_alternates": [],
"name": "heavy plus sign",
"shortname": ":heavy_plus_sign:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"calculation",
- "math"
+ "math",
+ "symbol"
],
"moji": "➕"
},
@@ -14380,7 +15949,7 @@
"unicode_alternates": [],
"name": "helicopter",
"shortname": ":helicopter:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -14389,13 +15958,16 @@
"helicopter",
"helo",
"gyro",
- "gyrocopter"
+ "gyrocopter",
+ "plane",
+ "travel",
+ "fly"
],
"moji": "🚁"
},
"helmet_with_cross": {
"unicode": "26D1",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "helmet with white cross",
"shortname": ":helmet_with_cross:",
"category": "people",
@@ -14407,8 +15979,12 @@
"aid",
"face",
"hat",
- "person"
- ]
+ "person",
+ "object",
+ "accessories",
+ "job"
+ ],
+ "moji": "⛑"
},
"herb": {
"unicode": "1F33F",
@@ -14427,9 +16003,10 @@
"weed",
"herb",
"spice",
- "plant",
"cook",
- "cooking"
+ "cooking",
+ "nature",
+ "leaf"
],
"moji": "🌿"
},
@@ -14447,7 +16024,9 @@
"vegetable",
"hibiscus",
"flower",
- "warm"
+ "warm",
+ "nature",
+ "tropical"
],
"moji": "🌺"
},
@@ -14456,13 +16035,14 @@
"unicode_alternates": [],
"name": "high brightness symbol",
"shortname": ":high_brightness:",
- "category": "objects",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"light",
"summer",
- "sun"
+ "sun",
+ "symbol"
],
"moji": "🔆"
},
@@ -14471,45 +16051,57 @@
"unicode_alternates": [],
"name": "high-heeled shoe",
"shortname": ":high_heel:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"fashion",
"female",
- "shoes"
+ "shoes",
+ "women",
+ "shoe",
+ "sexy",
+ "accessories",
+ "girls night"
],
"moji": "👠"
},
"hockey": {
"unicode": "1F3D2",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "ice hockey stick and puck",
"shortname": ":hockey:",
"category": "activity",
"aliases": [],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "game",
+ "sport",
+ "hockey"
+ ],
+ "moji": "🏒"
},
"hole": {
"unicode": "1F573",
"unicode_alternates": [],
"name": "hole",
"shortname": ":hole:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"pit",
- "well"
- ]
+ "well",
+ "object"
+ ],
+ "moji": "🕳"
},
"homes": {
"unicode": "1F3D8",
"unicode_alternates": [],
"name": "house buildings",
"shortname": ":homes:",
- "category": "travel_places",
+ "category": "travel",
"aliases": [
":house_buildings:"
],
@@ -14521,15 +16113,19 @@
"mansion",
"bungalow",
"ranch",
- "craftsman"
- ]
+ "craftsman",
+ "places",
+ "building",
+ "house"
+ ],
+ "moji": "🏘"
},
"honey_pot": {
"unicode": "1F36F",
"unicode_alternates": [],
"name": "honey pot",
"shortname": ":honey_pot:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -14537,9 +16133,10 @@
"sweet",
"honey",
"pot",
- "bees",
"pooh",
- "bear"
+ "bear",
+ "food",
+ "vagina"
],
"moji": "🍯"
},
@@ -14553,7 +16150,8 @@
"aliases_ascii": [],
"keywords": [
"animal",
- "brown"
+ "brown",
+ "wildlife"
],
"moji": "🐴"
},
@@ -14562,7 +16160,7 @@
"unicode_alternates": [],
"name": "horse racing",
"shortname": ":horse_racing:",
- "category": "objects",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -14573,13 +16171,16 @@
"race",
"racing",
"jockey",
- "triple crown"
+ "triple crown",
+ "men",
+ "sport",
+ "horse racing"
],
"moji": "🏇"
},
"horse_racing_tone1": {
"unicode": "1F3C7-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "horse racing tone 1",
"shortname": ":horse_racing_tone1:",
"category": "activity",
@@ -14592,11 +16193,12 @@
"race",
"jockey",
"triple crown"
- ]
+ ],
+ "moji": "🏇🏻"
},
"horse_racing_tone2": {
"unicode": "1F3C7-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "horse racing tone 2",
"shortname": ":horse_racing_tone2:",
"category": "activity",
@@ -14609,11 +16211,12 @@
"race",
"jockey",
"triple crown"
- ]
+ ],
+ "moji": "🏇🏼"
},
"horse_racing_tone3": {
"unicode": "1F3C7-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "horse racing tone 3",
"shortname": ":horse_racing_tone3:",
"category": "activity",
@@ -14626,11 +16229,12 @@
"race",
"jockey",
"triple crown"
- ]
+ ],
+ "moji": "🏇🏽"
},
"horse_racing_tone4": {
"unicode": "1F3C7-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "horse racing tone 4",
"shortname": ":horse_racing_tone4:",
"category": "activity",
@@ -14643,11 +16247,12 @@
"race",
"jockey",
"triple crown"
- ]
+ ],
+ "moji": "🏇🏾"
},
"horse_racing_tone5": {
"unicode": "1F3C7-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "horse racing tone 5",
"shortname": ":horse_racing_tone5:",
"category": "activity",
@@ -14660,21 +16265,24 @@
"race",
"jockey",
"triple crown"
- ]
+ ],
+ "moji": "🏇🏿"
},
"hospital": {
"unicode": "1F3E5",
"unicode_alternates": [],
"name": "hospital",
"shortname": ":hospital:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"building",
"doctor",
"health",
- "surgery"
+ "surgery",
+ "places",
+ "911"
],
"moji": "🏥"
},
@@ -14683,7 +16291,7 @@
"unicode_alternates": [],
"name": "hot pepper",
"shortname": ":hot_pepper:",
- "category": "food_drink",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -14693,27 +16301,33 @@
"chili",
"cayenne",
"habanero",
- "jalapeno"
- ]
+ "jalapeno",
+ "vegetables"
+ ],
+ "moji": "🌶"
},
"hotdog": {
"unicode": "1F32D",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "hot dog",
"shortname": ":hotdog:",
- "category": "foods",
+ "category": "food",
"aliases": [
":hot_dog:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "america",
+ "food"
+ ],
+ "moji": "🌭"
},
"hotel": {
"unicode": "1F3E8",
"unicode_alternates": [],
"name": "hotel",
"shortname": ":hotel:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -14724,7 +16338,9 @@
"hotel",
"motel",
"holiday inn",
- "hospital"
+ "hospital",
+ "places",
+ "vacation"
],
"moji": "🏨"
},
@@ -14735,13 +16351,14 @@
],
"name": "hot springs",
"shortname": ":hotsprings:",
- "category": "places",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"bath",
"relax",
- "warm"
+ "warm",
+ "symbol"
],
"moji": "♨"
},
@@ -14758,7 +16375,8 @@
"keywords": [
"clock",
"oldschool",
- "time"
+ "time",
+ "object"
],
"moji": "⌛"
},
@@ -14773,7 +16391,8 @@
"keywords": [
"countdown",
"oldschool",
- "time"
+ "time",
+ "object"
],
"moji": "⏳"
},
@@ -14782,20 +16401,20 @@
"unicode_alternates": [],
"name": "house building",
"shortname": ":house:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"building",
"home",
"house",
- "home",
"residence",
"dwelling",
"mansion",
"bungalow",
"ranch",
- "craftsman"
+ "craftsman",
+ "places"
],
"moji": "🏠"
},
@@ -14804,7 +16423,7 @@
"unicode_alternates": [],
"name": "derelict house building",
"shortname": ":house_abandoned:",
- "category": "travel_places",
+ "category": "travel",
"aliases": [
":derelict_house_building:"
],
@@ -14821,27 +16440,34 @@
"abandoned",
"vacant",
"run down",
- "shoddy"
- ]
+ "shoddy",
+ "places",
+ "building",
+ "house"
+ ],
+ "moji": "🏚"
},
"house_with_garden": {
"unicode": "1F3E1",
"unicode_alternates": [],
"name": "house with garden",
"shortname": ":house_with_garden:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"home",
"nature",
- "plant"
+ "plant",
+ "places",
+ "building",
+ "house"
],
"moji": "🏡"
},
"hugging": {
"unicode": "1F917",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "hugging face",
"shortname": ":hugging:",
"category": "people",
@@ -14849,14 +16475,19 @@
":hugging_face:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "smiley",
+ "hug",
+ "thank you"
+ ],
+ "moji": "🤗"
},
"hushed": {
"unicode": "1F62F",
"unicode_alternates": [],
"name": "hushed face",
"shortname": ":hushed:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -14865,7 +16496,10 @@
"quiet",
"hush",
"whisper",
- "silent"
+ "silent",
+ "smiley",
+ "surprised",
+ "wow"
],
"moji": "😯"
},
@@ -14874,7 +16508,7 @@
"unicode_alternates": [],
"name": "ice cream",
"shortname": ":ice_cream:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -14896,7 +16530,7 @@
},
"ice_skate": {
"unicode": "26F8",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "ice skate",
"shortname": ":ice_skate:",
"category": "activity",
@@ -14905,15 +16539,18 @@
"keywords": [
"place",
"sport",
- "travel"
- ]
+ "travel",
+ "cold",
+ "ice skating"
+ ],
+ "moji": "⛸"
},
"icecream": {
"unicode": "1F366",
"unicode_alternates": [],
"name": "soft ice cream",
"shortname": ":icecream:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -14935,7 +16572,7 @@
},
"id": {
"unicode": "1F194",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "squared id",
"shortname": ":id:",
"category": "symbols",
@@ -14947,21 +16584,24 @@
"identity",
"symbol",
"word"
- ]
+ ],
+ "moji": "🆔"
},
"ideograph_advantage": {
"unicode": "1F250",
"unicode_alternates": [],
"name": "circled ideograph advantage",
"shortname": ":ideograph_advantage:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"chinese",
"get",
"kanji",
- "obtain"
+ "obtain",
+ "japan",
+ "symbol"
],
"moji": "🉐"
},
@@ -14970,7 +16610,7 @@
"unicode_alternates": [],
"name": "imp",
"shortname": ":imp:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -14979,7 +16619,9 @@
"evil",
"horns",
"cute",
- "devil"
+ "smiley",
+ "monster",
+ "wth"
],
"moji": "👿"
},
@@ -14993,7 +16635,9 @@
"aliases_ascii": [],
"keywords": [
"documents",
- "email"
+ "email",
+ "work",
+ "office"
],
"moji": "📥"
},
@@ -15007,30 +16651,17 @@
"aliases_ascii": [],
"keywords": [
"email",
- "inbox"
+ "inbox",
+ "object"
],
"moji": "📨"
},
- "info": {
- "unicode": "1F6C8",
- "unicode_alternates": [],
- "name": "circled information source",
- "shortname": ":info:",
- "category": "objects_symbols",
- "aliases": [
- ":circled_information_source:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "icon"
- ]
- },
"information_desk_person": {
"unicode": "1F481",
"unicode_alternates": [],
"name": "information desk person",
"shortname": ":information_desk_person:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -15045,13 +16676,16 @@
"sassy",
"unimpressed",
"attitude",
- "snarky"
+ "snarky",
+ "people",
+ "women",
+ "diversity"
],
"moji": "💁"
},
"information_desk_person_tone1": {
"unicode": "1F481-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "information desk person tone 1",
"shortname": ":information_desk_person_tone1:",
"category": "people",
@@ -15069,11 +16703,12 @@
"unimpressed",
"attitude",
"snarky"
- ]
+ ],
+ "moji": "💁🏻"
},
"information_desk_person_tone2": {
"unicode": "1F481-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "information desk person tone 2",
"shortname": ":information_desk_person_tone2:",
"category": "people",
@@ -15091,11 +16726,12 @@
"unimpressed",
"attitude",
"snarky"
- ]
+ ],
+ "moji": "💁🏼"
},
"information_desk_person_tone3": {
"unicode": "1F481-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "information desk person tone 3",
"shortname": ":information_desk_person_tone3:",
"category": "people",
@@ -15113,11 +16749,12 @@
"unimpressed",
"attitude",
"snarky"
- ]
+ ],
+ "moji": "💁🏽"
},
"information_desk_person_tone4": {
"unicode": "1F481-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "information desk person tone 4",
"shortname": ":information_desk_person_tone4:",
"category": "people",
@@ -15135,11 +16772,12 @@
"unimpressed",
"attitude",
"snarky"
- ]
+ ],
+ "moji": "💁🏾"
},
"information_desk_person_tone5": {
"unicode": "1F481-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "information desk person tone 5",
"shortname": ":information_desk_person_tone5:",
"category": "people",
@@ -15157,7 +16795,8 @@
"unimpressed",
"attitude",
"snarky"
- ]
+ ],
+ "moji": "💁🏿"
},
"information_source": {
"unicode": "2139",
@@ -15166,13 +16805,14 @@
],
"name": "information source",
"shortname": ":information_source:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"alphabet",
"blue-square",
- "letter"
+ "letter",
+ "symbol"
],
"moji": "ℹ"
},
@@ -15181,7 +16821,7 @@
"unicode_alternates": [],
"name": "smiling face with halo",
"shortname": ":innocent:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [
"O:-)",
@@ -15202,12 +16842,12 @@
"angel",
"face",
"halo",
- "halo",
- "angel",
"innocent",
"ring",
"circle",
- "heaven"
+ "heaven",
+ "smiley",
+ "emotion"
],
"moji": "😇"
},
@@ -15218,13 +16858,14 @@
],
"name": "exclamation question mark",
"shortname": ":interrobang:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"punctuation",
"surprise",
- "wat"
+ "wat",
+ "symbol"
],
"moji": "⁉"
},
@@ -15240,7 +16881,10 @@
"apple",
"dial",
"gadgets",
- "technology"
+ "technology",
+ "electronics",
+ "phone",
+ "selfie"
],
"moji": "📱"
},
@@ -15249,7 +16893,7 @@
"unicode_alternates": [],
"name": "desert island",
"shortname": ":island:",
- "category": "travel_places",
+ "category": "travel",
"aliases": [
":desert_island:"
],
@@ -15257,15 +16901,22 @@
"keywords": [
"land",
"solitude",
- "alone"
- ]
+ "alone",
+ "places",
+ "travel",
+ "vacation",
+ "tropical",
+ "beach",
+ "swim"
+ ],
+ "moji": "🏝"
},
"izakaya_lantern": {
"unicode": "1F3EE",
"unicode_alternates": [],
"name": "izakaya lantern",
"shortname": ":izakaya_lantern:",
- "category": "places",
+ "category": "objects",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -15277,7 +16928,9 @@
"alcohol",
"bar",
"sake",
- "restaurant"
+ "restaurant",
+ "object",
+ "japan"
],
"moji": "🏮"
},
@@ -15286,14 +16939,13 @@
"unicode_alternates": [],
"name": "jack-o-lantern",
"shortname": ":jack_o_lantern:",
- "category": "objects",
+ "category": "nature",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"halloween",
"jack-o-lantern",
"pumpkin",
- "halloween",
"holiday",
"carve",
"autumn",
@@ -15305,7 +16957,8 @@
"horror",
"scary",
"scared",
- "dead"
+ "dead",
+ "holidays"
],
"moji": "🎃"
},
@@ -15314,11 +16967,16 @@
"unicode_alternates": [],
"name": "silhouette of japan",
"shortname": ":japan:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "nation"
+ "nation",
+ "places",
+ "travel",
+ "map",
+ "vacation",
+ "tropical"
],
"moji": "🗾"
},
@@ -15327,7 +16985,7 @@
"unicode_alternates": [],
"name": "japanese castle",
"shortname": ":japanese_castle:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -15339,7 +16997,10 @@
"royalty",
"fort",
"fortified",
- "fortress"
+ "fortress",
+ "places",
+ "travel",
+ "vacation"
],
"moji": "🏯"
},
@@ -15348,7 +17009,7 @@
"unicode_alternates": [],
"name": "japanese goblin",
"shortname": ":japanese_goblin:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -15361,13 +17022,14 @@
"avian",
"demon",
"goblin",
- "mask",
"theater",
"nose",
"frown",
"mustache",
"anger",
- "frustration"
+ "frustration",
+ "angry",
+ "monster"
],
"moji": "👺"
},
@@ -15376,7 +17038,7 @@
"unicode_alternates": [],
"name": "japanese ogre",
"shortname": ":japanese_ogre:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -15387,7 +17049,6 @@
"troll",
"ogre",
"folklore",
- "monster",
"devil",
"mask",
"theater",
@@ -15401,7 +17062,7 @@
"unicode_alternates": [],
"name": "jeans",
"shortname": ":jeans:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -15419,26 +17080,12 @@
],
"moji": "👖"
},
- "jet_up": {
- "unicode": "1F6E6",
- "unicode_alternates": [],
- "name": "up-pointing military airplane",
- "shortname": ":jet_up:",
- "category": "travel_places",
- "aliases": [
- ":up_pointing_military_airplane:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "jet"
- ]
- },
"joy": {
"unicode": "1F602",
"unicode_alternates": [],
"name": "face with tears of joy",
"shortname": ":joy:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [
":')",
@@ -15450,11 +17097,13 @@
"haha",
"happy",
"tears",
- "tears",
- "cry",
"joy",
- "happy",
- "weep"
+ "weep",
+ "silly",
+ "smiley",
+ "laugh",
+ "emotion",
+ "sarcastic"
],
"moji": "😂"
},
@@ -15463,7 +17112,7 @@
"unicode_alternates": [],
"name": "cat face with tears of joy",
"shortname": ":joy_cat:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -15472,10 +17121,12 @@
"haha",
"happy",
"tears",
- "happy",
- "tears",
"cry",
- "joy"
+ "joy",
+ "silly",
+ "laugh",
+ "cat",
+ "sarcastic"
],
"moji": "😹"
},
@@ -15484,24 +17135,112 @@
"unicode_alternates": [],
"name": "joystick",
"shortname": ":joystick:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"games",
"atari",
- "controller"
- ]
+ "controller",
+ "electronics",
+ "game",
+ "boys night"
+ ],
+ "moji": "🕹"
+ },
+ "juggling": {
+ "unicode": "1F939",
+ "unicode_alternates": [],
+ "name": "juggling",
+ "shortname": ":juggling:",
+ "category": "activity",
+ "aliases": [
+ ":juggler:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤹"
+ },
+ "juggling_tone1": {
+ "unicode": "1F939-1F3FB",
+ "unicode_alternates": [],
+ "name": "juggling tone 1",
+ "shortname": ":juggling_tone1:",
+ "category": "activity",
+ "aliases": [
+ ":juggler_tone1:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤹🏻"
+ },
+ "juggling_tone2": {
+ "unicode": "1F939-1F3FC",
+ "unicode_alternates": [],
+ "name": "juggling tone 2",
+ "shortname": ":juggling_tone2:",
+ "category": "activity",
+ "aliases": [
+ ":juggler_tone2:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤹🏼"
+ },
+ "juggling_tone3": {
+ "unicode": "1F939-1F3FD",
+ "unicode_alternates": [],
+ "name": "juggling tone 3",
+ "shortname": ":juggling_tone3:",
+ "category": "activity",
+ "aliases": [
+ ":juggler_tone3:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤹🏽"
+ },
+ "juggling_tone4": {
+ "unicode": "1F939-1F3FE",
+ "unicode_alternates": [],
+ "name": "juggling tone 4",
+ "shortname": ":juggling_tone4:",
+ "category": "activity",
+ "aliases": [
+ ":juggler_tone4:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤹🏾"
+ },
+ "juggling_tone5": {
+ "unicode": "1F939-1F3FF",
+ "unicode_alternates": [],
+ "name": "juggling tone 5",
+ "shortname": ":juggling_tone5:",
+ "category": "activity",
+ "aliases": [
+ ":juggler_tone5:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤹🏿"
},
"kaaba": {
"unicode": "1F54B",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "kaaba",
"shortname": ":kaaba:",
"category": "travel",
"aliases": [],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "places",
+ "religion",
+ "building",
+ "condolence"
+ ],
+ "moji": "🕋"
},
"key": {
"unicode": "1F511",
@@ -15514,7 +17253,8 @@
"keywords": [
"door",
"lock",
- "password"
+ "password",
+ "object"
],
"moji": "🔑"
},
@@ -15523,7 +17263,7 @@
"unicode_alternates": [],
"name": "old key",
"shortname": ":key2:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [
":old_key:"
],
@@ -15532,79 +17272,34 @@
"door",
"lock",
"password",
- "skeleton"
- ]
- },
- "keyboard": {
- "unicode": "1F5AE",
- "unicode_alternates": [],
- "name": "wired keyboard",
- "shortname": ":keyboard:",
- "category": "objects_symbols",
- "aliases": [
- ":wired_keyboard:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "typing",
- "keys",
- "input",
- "device"
- ]
- },
- "keyboard_mouse": {
- "unicode": "1F5A6",
- "unicode_alternates": [],
- "name": "keyboard and mouse",
- "shortname": ":keyboard_mouse:",
- "category": "objects_symbols",
- "aliases": [
- ":keyboard_and_mouse:"
+ "skeleton",
+ "object"
],
- "aliases_ascii": [],
- "keywords": [
- "computer",
- "input",
- "desktop"
- ]
+ "moji": "🗝"
},
- "keyboard_with_jacks": {
- "unicode": "1F398",
- "unicode_alternates": [],
- "name": "musical keyboard with jacks",
- "shortname": ":keyboard_with_jacks:",
- "category": "objects_symbols",
- "aliases": [
- ":musical_keyboard_with_jacks:"
+ "keyboard": {
+ "unicode": "2328",
+ "unicode_alternates": [
+ "2328-FE0F"
],
- "aliases_ascii": [],
- "keywords": [
- "music",
- "instrument",
- "midi"
- ]
- },
- "keycap_ten": {
- "unicode": "1F51F",
- "unicode_alternates": [],
- "name": "keycap ten",
- "shortname": ":keycap_ten:",
- "category": "other",
+ "name": "keyboard",
+ "shortname": ":keyboard:",
+ "category": "objects",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "10",
- "blue-square",
- "numbers"
+ "electronics",
+ "work",
+ "office"
],
- "moji": "🔟"
+ "moji": "⌨"
},
"kimono": {
"unicode": "1F458",
"unicode_alternates": [],
"name": "kimono",
"shortname": ":kimono:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -15621,7 +17316,7 @@
"unicode_alternates": [],
"name": "kiss mark",
"shortname": ":kiss:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -15630,7 +17325,12 @@
"like",
"lips",
"love",
- "valentines"
+ "valentines",
+ "women",
+ "sexy",
+ "lip",
+ "beautiful",
+ "girls night"
],
"moji": "💋"
},
@@ -15652,8 +17352,14 @@
"love",
"marriage",
"valentines",
- "couple"
- ]
+ "couple",
+ "people",
+ "gay",
+ "men",
+ "sex",
+ "lgbt"
+ ],
+ "moji": "👨‍❤️‍💋‍👨"
},
"kiss_ww": {
"unicode": "1F469-2764-1F48B-1F469",
@@ -15673,15 +17379,21 @@
"love",
"marriage",
"valentines",
- "couple"
- ]
+ "couple",
+ "people",
+ "women",
+ "sex",
+ "lgbt",
+ "lesbian"
+ ],
+ "moji": "👩‍❤️‍💋‍👩"
},
"kissing": {
"unicode": "1F617",
"unicode_alternates": [],
"name": "kissing face",
"shortname": ":kissing:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -15695,7 +17407,9 @@
"kiss",
"pucker",
"lips",
- "smooch"
+ "smooch",
+ "smiley",
+ "sexy"
],
"moji": "😗"
},
@@ -15704,7 +17418,7 @@
"unicode_alternates": [],
"name": "kissing cat face with closed eyes",
"shortname": ":kissing_cat:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -15714,7 +17428,8 @@
"kiss",
"puckered",
"heart",
- "love"
+ "love",
+ "cat"
],
"moji": "😽"
},
@@ -15723,7 +17438,7 @@
"unicode_alternates": [],
"name": "kissing face with closed eyes",
"shortname": ":kissing_closed_eyes:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -15738,8 +17453,9 @@
"passion",
"puckered",
"heart",
- "love",
- "smooch"
+ "smooch",
+ "smiley",
+ "sexy"
],
"moji": "😚"
},
@@ -15748,7 +17464,7 @@
"unicode_alternates": [],
"name": "face throwing a kiss",
"shortname": ":kissing_heart:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [
":*",
@@ -15766,8 +17482,9 @@
"love",
"lips",
"like",
- "love",
- "valentines"
+ "valentines",
+ "smiley",
+ "sexy"
],
"moji": "😘"
},
@@ -15776,7 +17493,7 @@
"unicode_alternates": [],
"name": "kissing face with smiling eyes",
"shortname": ":kissing_smiling_eyes:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -15789,10 +17506,25 @@
"smile",
"pucker",
"lips",
- "smooch"
+ "smooch",
+ "smiley",
+ "sexy"
],
"moji": "😙"
},
+ "kiwi": {
+ "unicode": "1F95D",
+ "unicode_alternates": [],
+ "name": "kiwifruit",
+ "shortname": ":kiwi:",
+ "category": "food",
+ "aliases": [
+ ":kiwifruit:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🥝"
+ },
"knife": {
"unicode": "1F52A",
"unicode_alternates": [],
@@ -15801,7 +17533,10 @@
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": [],
+ "keywords": [
+ "object",
+ "weapon"
+ ],
"moji": "🔪"
},
"koala": {
@@ -15814,7 +17549,8 @@
"aliases_ascii": [],
"keywords": [
"animal",
- "nature"
+ "nature",
+ "wildlife"
],
"moji": "🐨"
},
@@ -15823,7 +17559,7 @@
"unicode_alternates": [],
"name": "squared katakana koko",
"shortname": ":koko:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -15831,7 +17567,8 @@
"destination",
"here",
"japanese",
- "katakana"
+ "katakana",
+ "symbol"
],
"moji": "🈁"
},
@@ -15840,22 +17577,28 @@
"unicode_alternates": [],
"name": "label",
"shortname": ":label:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "tag"
- ]
+ "tag",
+ "object"
+ ],
+ "moji": "🏷"
},
"large_blue_circle": {
"unicode": "1F535",
"unicode_alternates": [],
"name": "large blue circle",
"shortname": ":large_blue_circle:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": [],
+ "keywords": [
+ "shapes",
+ "symbol",
+ "circle"
+ ],
"moji": "🔵"
},
"large_blue_diamond": {
@@ -15863,11 +17606,13 @@
"unicode_alternates": [],
"name": "large blue diamond",
"shortname": ":large_blue_diamond:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "shape"
+ "shape",
+ "shapes",
+ "symbol"
],
"moji": "🔷"
},
@@ -15876,11 +17621,13 @@
"unicode_alternates": [],
"name": "large orange diamond",
"shortname": ":large_orange_diamond:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "shape"
+ "shape",
+ "shapes",
+ "symbol"
],
"moji": "🔶"
},
@@ -15900,7 +17647,8 @@
"sky",
"night",
"cheese",
- "phase"
+ "phase",
+ "space"
],
"moji": "🌗"
},
@@ -15922,7 +17670,8 @@
"sky",
"night",
"cheese",
- "phase"
+ "phase",
+ "space"
],
"moji": "🌜"
},
@@ -15931,7 +17680,7 @@
"unicode_alternates": [],
"name": "smiling face with open mouth and tightly-closed ey",
"shortname": ":laughing:",
- "category": "emoticons",
+ "category": "people",
"aliases": [
":satisfied:"
],
@@ -15947,7 +17696,9 @@
"lol",
"smiling",
"laughing",
- "laugh"
+ "laugh",
+ "smiley",
+ "emotion"
],
"moji": "😆"
},
@@ -15984,16 +17735,97 @@
"aliases_ascii": [],
"keywords": [
"notes",
- "paper"
+ "paper",
+ "object",
+ "office",
+ "write"
],
"moji": "📒"
},
+ "left_facing_fist": {
+ "unicode": "1F91B",
+ "unicode_alternates": [],
+ "name": "left-facing fist",
+ "shortname": ":left_facing_fist:",
+ "category": "people",
+ "aliases": [
+ ":left_fist:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤛"
+ },
+ "left_facing_fist_tone1": {
+ "unicode": "1F91B-1F3FB",
+ "unicode_alternates": [],
+ "name": "left facing fist tone 1",
+ "shortname": ":left_facing_fist_tone1:",
+ "category": "people",
+ "aliases": [
+ ":left_fist_tone1:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤛🏻"
+ },
+ "left_facing_fist_tone2": {
+ "unicode": "1F91B-1F3FC",
+ "unicode_alternates": [],
+ "name": "left facing fist tone 2",
+ "shortname": ":left_facing_fist_tone2:",
+ "category": "people",
+ "aliases": [
+ ":left_fist_tone2:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤛🏼"
+ },
+ "left_facing_fist_tone3": {
+ "unicode": "1F91B-1F3FD",
+ "unicode_alternates": [],
+ "name": "left facing fist tone 3",
+ "shortname": ":left_facing_fist_tone3:",
+ "category": "people",
+ "aliases": [
+ ":left_fist_tone3:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤛🏽"
+ },
+ "left_facing_fist_tone4": {
+ "unicode": "1F91B-1F3FE",
+ "unicode_alternates": [],
+ "name": "left facing fist tone 4",
+ "shortname": ":left_facing_fist_tone4:",
+ "category": "people",
+ "aliases": [
+ ":left_fist_tone4:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤛🏾"
+ },
+ "left_facing_fist_tone5": {
+ "unicode": "1F91B-1F3FF",
+ "unicode_alternates": [],
+ "name": "left facing fist tone 5",
+ "shortname": ":left_facing_fist_tone5:",
+ "category": "people",
+ "aliases": [
+ ":left_fist_tone5:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤛🏿"
+ },
"left_luggage": {
"unicode": "1F6C5",
"unicode_alternates": [],
"name": "left luggage",
"shortname": ":left_luggage:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -16002,26 +17834,10 @@
"bag",
"baggage",
"luggage",
- "travel"
+ "symbol"
],
"moji": "🛅"
},
- "left_receiver": {
- "unicode": "1F57B",
- "unicode_alternates": [],
- "name": "left hand telephone receiver",
- "shortname": ":left_receiver:",
- "category": "objects_symbols",
- "aliases": [
- ":left_hand_telephone_receiver:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "communication",
- "dial",
- "technology"
- ]
- },
"left_right_arrow": {
"unicode": "2194",
"unicode_alternates": [
@@ -16029,11 +17845,13 @@
],
"name": "left right arrow",
"shortname": ":left_right_arrow:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "shape"
+ "shape",
+ "arrow",
+ "symbol"
],
"moji": "↔"
},
@@ -16044,10 +17862,13 @@
],
"name": "leftwards arrow with hook",
"shortname": ":leftwards_arrow_with_hook:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": [],
+ "keywords": [
+ "arrow",
+ "symbol"
+ ],
"moji": "↩"
},
"lemon": {
@@ -16055,7 +17876,7 @@
"unicode_alternates": [],
"name": "lemon",
"shortname": ":lemon:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -16063,7 +17884,8 @@
"nature",
"lemon",
"yellow",
- "citrus"
+ "citrus",
+ "food"
],
"moji": "🍋"
},
@@ -16074,7 +17896,7 @@
],
"name": "leo",
"shortname": ":leo:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -16087,9 +17909,8 @@
"zodiac",
"sign",
"purple-square",
- "sign",
- "zodiac",
- "horoscope"
+ "horoscope",
+ "symbol"
],
"moji": "♌"
},
@@ -16108,7 +17929,9 @@
"cat",
"spot",
"spotted",
- "sexy"
+ "sexy",
+ "wildlife",
+ "roar"
],
"moji": "🐆"
},
@@ -16117,27 +17940,31 @@
"unicode_alternates": [],
"name": "level slider",
"shortname": ":level_slider:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"controls"
- ]
+ ],
+ "moji": "🎚"
},
"levitate": {
"unicode": "1F574",
"unicode_alternates": [],
"name": "man in business suit levitating",
"shortname": ":levitate:",
- "category": "people",
+ "category": "activity",
"aliases": [
":man_in_business_suit_levitating:"
],
"aliases_ascii": [],
"keywords": [
"hover",
- "exclamation"
- ]
+ "exclamation",
+ "men",
+ "job"
+ ],
+ "moji": "🕴"
},
"libra": {
"unicode": "264E",
@@ -16146,7 +17973,7 @@
],
"name": "libra",
"shortname": ":libra:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -16159,9 +17986,8 @@
"zodiac",
"sign",
"purple-square",
- "sign",
- "zodiac",
- "horoscope"
+ "horoscope",
+ "symbol"
],
"moji": "♎"
},
@@ -16179,12 +18005,20 @@
"bench",
"press",
"squats",
- "deadlift"
- ]
+ "deadlift",
+ "men",
+ "workout",
+ "flex",
+ "sport",
+ "weight lifting",
+ "win",
+ "diversity"
+ ],
+ "moji": "🏋"
},
"lifter_tone1": {
"unicode": "1F3CB-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "weight lifter tone 1",
"shortname": ":lifter_tone1:",
"category": "activity",
@@ -16197,11 +18031,12 @@
"press",
"squats",
"deadlift"
- ]
+ ],
+ "moji": "🏋🏻"
},
"lifter_tone2": {
"unicode": "1F3CB-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "weight lifter tone 2",
"shortname": ":lifter_tone2:",
"category": "activity",
@@ -16214,11 +18049,12 @@
"press",
"squats",
"deadlift"
- ]
+ ],
+ "moji": "🏋🏼"
},
"lifter_tone3": {
"unicode": "1F3CB-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "weight lifter tone 3",
"shortname": ":lifter_tone3:",
"category": "activity",
@@ -16231,11 +18067,12 @@
"press",
"squats",
"deadlift"
- ]
+ ],
+ "moji": "🏋🏽"
},
"lifter_tone4": {
"unicode": "1F3CB-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "weight lifter tone 4",
"shortname": ":lifter_tone4:",
"category": "activity",
@@ -16248,11 +18085,12 @@
"press",
"squats",
"deadlift"
- ]
+ ],
+ "moji": "🏋🏾"
},
"lifter_tone5": {
"unicode": "1F3CB-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "weight lifter tone 5",
"shortname": ":lifter_tone5:",
"category": "activity",
@@ -16265,28 +18103,15 @@
"press",
"squats",
"deadlift"
- ]
- },
- "light_check_mark": {
- "unicode": "1F5F8",
- "unicode_alternates": [],
- "name": "light check mark",
- "shortname": ":light_check_mark:",
- "category": "objects_symbols",
- "aliases": [
- ":light_mark:"
],
- "aliases_ascii": [],
- "keywords": [
- "vote"
- ]
+ "moji": "🏋🏿"
},
"light_rail": {
"unicode": "1F688",
"unicode_alternates": [],
"name": "light rail",
"shortname": ":light_rail:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -16294,7 +18119,8 @@
"vehicle",
"train",
"rail",
- "light"
+ "light",
+ "travel"
],
"moji": "🚈"
},
@@ -16303,18 +18129,20 @@
"unicode_alternates": [],
"name": "link symbol",
"shortname": ":link:",
- "category": "other",
+ "category": "objects",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"rings",
- "url"
+ "url",
+ "symbol",
+ "office"
],
"moji": "🔗"
},
"lion_face": {
"unicode": "1F981",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "lion face",
"shortname": ":lion_face:",
"category": "nature",
@@ -16322,50 +18150,62 @@
":lion:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "wildlife",
+ "roar",
+ "cat",
+ "animal"
+ ],
+ "moji": "🦁"
},
"lips": {
"unicode": "1F444",
"unicode_alternates": [],
"name": "mouth",
"shortname": ":lips:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"kiss",
- "mouth"
+ "mouth",
+ "women",
+ "body",
+ "sexy",
+ "lip"
],
"moji": "👄"
},
- "lips2": {
- "unicode": "1F5E2",
- "unicode_alternates": [],
- "name": "lips",
- "shortname": ":lips2:",
- "category": "people",
- "aliases": [],
- "aliases_ascii": [],
- "keywords": [
- "kiss",
- "mouth"
- ]
- },
"lipstick": {
"unicode": "1F484",
"unicode_alternates": [],
"name": "lipstick",
"shortname": ":lipstick:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"fashion",
"female",
- "girl"
+ "girl",
+ "object",
+ "women",
+ "sexy",
+ "lip"
],
"moji": "💄"
},
+ "lizard": {
+ "unicode": "1F98E",
+ "unicode_alternates": [],
+ "name": "lizard",
+ "shortname": ":lizard:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🦎"
+ },
"lock": {
"unicode": "1F512",
"unicode_alternates": [],
@@ -16376,7 +18216,9 @@
"aliases_ascii": [],
"keywords": [
"password",
- "security"
+ "security",
+ "object",
+ "lock"
],
"moji": "🔒"
},
@@ -16390,7 +18232,9 @@
"aliases_ascii": [],
"keywords": [
"secret",
- "security"
+ "security",
+ "object",
+ "lock"
],
"moji": "🔏"
},
@@ -16399,7 +18243,7 @@
"unicode_alternates": [],
"name": "lollipop",
"shortname": ":lollipop:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -16410,9 +18254,8 @@
"lollipop",
"stick",
"lick",
- "sweet",
"sugar",
- "candy"
+ "halloween"
],
"moji": "🍭"
},
@@ -16421,11 +18264,12 @@
"unicode_alternates": [],
"name": "double curly loop",
"shortname": ":loop:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "curly"
+ "curly",
+ "symbol"
],
"moji": "➿"
},
@@ -16434,10 +18278,13 @@
"unicode_alternates": [],
"name": "speaker with three sound waves",
"shortname": ":loud_sound:",
- "category": "objects",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": [],
+ "keywords": [
+ "alarm",
+ "symbol"
+ ],
"moji": "🔊"
},
"loudspeaker": {
@@ -16445,12 +18292,15 @@
"unicode_alternates": [],
"name": "public address loudspeaker",
"shortname": ":loudspeaker:",
- "category": "objects",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"sound",
- "volume"
+ "volume",
+ "object",
+ "alarm",
+ "symbol"
],
"moji": "📢"
},
@@ -16459,7 +18309,7 @@
"unicode_alternates": [],
"name": "love hotel",
"shortname": ":love_hotel:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -16468,7 +18318,6 @@
"like",
"love",
"hotel",
- "love",
"sex",
"romance",
"leisure",
@@ -16476,7 +18325,9 @@
"prostitution",
"hospital",
"birth",
- "happy"
+ "happy",
+ "places",
+ "building"
],
"moji": "🏩"
},
@@ -16485,7 +18336,7 @@
"unicode_alternates": [],
"name": "love letter",
"shortname": ":love_letter:",
- "category": "emoticons",
+ "category": "objects",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -16497,7 +18348,8 @@
"love",
"letter",
"kiss",
- "heart"
+ "heart",
+ "object"
],
"moji": "💌"
},
@@ -16506,15 +18358,29 @@
"unicode_alternates": [],
"name": "low brightness symbol",
"shortname": ":low_brightness:",
- "category": "objects",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"summer",
- "sun"
+ "sun",
+ "symbol"
],
"moji": "🔅"
},
+ "lying_face": {
+ "unicode": "1F925",
+ "unicode_alternates": [],
+ "name": "lying face",
+ "shortname": ":lying_face:",
+ "category": "people",
+ "aliases": [
+ ":liar:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤥"
+ },
"m": {
"unicode": "24C2",
"unicode_alternates": [
@@ -16522,13 +18388,14 @@
],
"name": "circled latin capital letter m",
"shortname": ":m:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"alphabet",
"blue-circle",
- "letter"
+ "letter",
+ "symbol"
],
"moji": "Ⓜ"
},
@@ -16546,7 +18413,8 @@
"detective",
"investigator",
"detail",
- "details"
+ "details",
+ "object"
],
"moji": "🔍"
},
@@ -16564,7 +18432,8 @@
"detective",
"investigator",
"detail",
- "details"
+ "details",
+ "object"
],
"moji": "🔎"
},
@@ -16575,13 +18444,15 @@
],
"name": "mahjong tile red dragon",
"shortname": ":mahjong:",
- "category": "objects",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"chinese",
"game",
- "kanji"
+ "kanji",
+ "object",
+ "symbol"
],
"moji": "🀄"
},
@@ -16596,7 +18467,8 @@
"keywords": [
"communication",
"email",
- "inbox"
+ "inbox",
+ "object"
],
"moji": "📫"
},
@@ -16611,7 +18483,9 @@
"keywords": [
"communication",
"email",
- "inbox"
+ "inbox",
+ "object",
+ "office"
],
"moji": "📪"
},
@@ -16626,7 +18500,8 @@
"keywords": [
"communication",
"email",
- "inbox"
+ "inbox",
+ "object"
],
"moji": "📬"
},
@@ -16640,7 +18515,8 @@
"aliases_ascii": [],
"keywords": [
"email",
- "inbox"
+ "inbox",
+ "object"
],
"moji": "📭"
},
@@ -16649,7 +18525,7 @@
"unicode_alternates": [],
"name": "man",
"shortname": ":man:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -16657,13 +18533,173 @@
"dad",
"father",
"guy",
- "mustashe"
+ "mustashe",
+ "people",
+ "men",
+ "sex",
+ "diversity",
+ "selfie",
+ "boys night"
],
"moji": "👨"
},
+ "man_dancing": {
+ "unicode": "1F57A",
+ "unicode_alternates": [],
+ "name": "man dancing",
+ "shortname": ":man_dancing:",
+ "category": "people",
+ "aliases": [
+ ":male_dancer:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🕺"
+ },
+ "man_dancing_tone1": {
+ "unicode": "1F57A-1F3FB",
+ "unicode_alternates": [],
+ "name": "man dancing tone 1",
+ "shortname": ":man_dancing_tone1:",
+ "category": "activity",
+ "aliases": [
+ ":male_dancer_tone1:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🕺🏻"
+ },
+ "man_dancing_tone2": {
+ "unicode": "1F57A-1F3FC",
+ "unicode_alternates": [],
+ "name": "man dancing tone 2",
+ "shortname": ":man_dancing_tone2:",
+ "category": "activity",
+ "aliases": [
+ ":male_dancer_tone2:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🕺🏼"
+ },
+ "man_dancing_tone3": {
+ "unicode": "1F57A-1F3FD",
+ "unicode_alternates": [],
+ "name": "man dancing tone 3",
+ "shortname": ":man_dancing_tone3:",
+ "category": "activity",
+ "aliases": [
+ ":male_dancer_tone3:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🕺🏽"
+ },
+ "man_dancing_tone4": {
+ "unicode": "1F57A-1F3FE",
+ "unicode_alternates": [],
+ "name": "man dancing tone 4",
+ "shortname": ":man_dancing_tone4:",
+ "category": "activity",
+ "aliases": [
+ ":male_dancer_tone4:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🕺🏾"
+ },
+ "man_dancing_tone5": {
+ "unicode": "1F57A-1F3FF",
+ "unicode_alternates": [],
+ "name": "man dancing tone 5",
+ "shortname": ":man_dancing_tone5:",
+ "category": "activity",
+ "aliases": [
+ ":male_dancer_tone5:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🕺🏿"
+ },
+ "man_in_tuxedo": {
+ "unicode": "1F935",
+ "unicode_alternates": [],
+ "name": "man in tuxedo",
+ "shortname": ":man_in_tuxedo:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤵"
+ },
+ "man_in_tuxedo_tone1": {
+ "unicode": "1F935-1F3FB",
+ "unicode_alternates": [],
+ "name": "man in tuxedo tone 1",
+ "shortname": ":man_in_tuxedo_tone1:",
+ "category": "people",
+ "aliases": [
+ ":tuxedo_tone1:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤵🏻"
+ },
+ "man_in_tuxedo_tone2": {
+ "unicode": "1F935-1F3FC",
+ "unicode_alternates": [],
+ "name": "man in tuxedo tone 2",
+ "shortname": ":man_in_tuxedo_tone2:",
+ "category": "people",
+ "aliases": [
+ ":tuxedo_tone2:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤵🏼"
+ },
+ "man_in_tuxedo_tone3": {
+ "unicode": "1F935-1F3FD",
+ "unicode_alternates": [],
+ "name": "man in tuxedo tone 3",
+ "shortname": ":man_in_tuxedo_tone3:",
+ "category": "people",
+ "aliases": [
+ ":tuxedo_tone3:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤵🏽"
+ },
+ "man_in_tuxedo_tone4": {
+ "unicode": "1F935-1F3FE",
+ "unicode_alternates": [],
+ "name": "man in tuxedo tone 4",
+ "shortname": ":man_in_tuxedo_tone4:",
+ "category": "people",
+ "aliases": [
+ ":tuxedo_tone4:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤵🏾"
+ },
+ "man_in_tuxedo_tone5": {
+ "unicode": "1F935-1F3FF",
+ "unicode_alternates": [],
+ "name": "man in tuxedo tone 5",
+ "shortname": ":man_in_tuxedo_tone5:",
+ "category": "people",
+ "aliases": [
+ ":tuxedo_tone5:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤵🏿"
+ },
"man_tone1": {
"unicode": "1F468-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "man tone 1",
"shortname": ":man_tone1:",
"category": "people",
@@ -16675,11 +18711,12 @@
"father",
"guy",
"mustache"
- ]
+ ],
+ "moji": "👨🏻"
},
"man_tone2": {
"unicode": "1F468-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "man tone 2",
"shortname": ":man_tone2:",
"category": "people",
@@ -16691,11 +18728,12 @@
"father",
"guy",
"mustache"
- ]
+ ],
+ "moji": "👨🏼"
},
"man_tone3": {
"unicode": "1F468-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "man tone 3",
"shortname": ":man_tone3:",
"category": "people",
@@ -16707,11 +18745,12 @@
"father",
"guy",
"mustache"
- ]
+ ],
+ "moji": "👨🏽"
},
"man_tone4": {
"unicode": "1F468-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "man tone 4",
"shortname": ":man_tone4:",
"category": "people",
@@ -16723,11 +18762,12 @@
"father",
"guy",
"mustache"
- ]
+ ],
+ "moji": "👨🏾"
},
"man_tone5": {
"unicode": "1F468-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "man tone 5",
"shortname": ":man_tone5:",
"category": "people",
@@ -16739,14 +18779,15 @@
"father",
"guy",
"mustache"
- ]
+ ],
+ "moji": "👨🏿"
},
"man_with_gua_pi_mao": {
"unicode": "1F472",
"unicode_alternates": [],
"name": "man with gua pi mao",
"shortname": ":man_with_gua_pi_mao:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -16755,13 +18796,17 @@
"skullcap",
"chinese",
"asian",
- "qing"
+ "qing",
+ "people",
+ "hat",
+ "men",
+ "diversity"
],
"moji": "👲"
},
"man_with_gua_pi_mao_tone1": {
"unicode": "1F472-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "man with gua pi mao tone 1",
"shortname": ":man_with_gua_pi_mao_tone1:",
"category": "people",
@@ -16774,11 +18819,12 @@
"chinese",
"asian",
"qing"
- ]
+ ],
+ "moji": "👲🏻"
},
"man_with_gua_pi_mao_tone2": {
"unicode": "1F472-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "man with gua pi mao tone 2",
"shortname": ":man_with_gua_pi_mao_tone2:",
"category": "people",
@@ -16791,11 +18837,12 @@
"chinese",
"asian",
"qing"
- ]
+ ],
+ "moji": "👲🏼"
},
"man_with_gua_pi_mao_tone3": {
"unicode": "1F472-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "man with gua pi mao tone 3",
"shortname": ":man_with_gua_pi_mao_tone3:",
"category": "people",
@@ -16808,11 +18855,12 @@
"chinese",
"asian",
"qing"
- ]
+ ],
+ "moji": "👲🏽"
},
"man_with_gua_pi_mao_tone4": {
"unicode": "1F472-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "man with gua pi mao tone 4",
"shortname": ":man_with_gua_pi_mao_tone4:",
"category": "people",
@@ -16825,11 +18873,12 @@
"chinese",
"asian",
"qing"
- ]
+ ],
+ "moji": "👲🏾"
},
"man_with_gua_pi_mao_tone5": {
"unicode": "1F472-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "man with gua pi mao tone 5",
"shortname": ":man_with_gua_pi_mao_tone5:",
"category": "people",
@@ -16842,14 +18891,15 @@
"chinese",
"asian",
"qing"
- ]
+ ],
+ "moji": "👲🏿"
},
"man_with_turban": {
"unicode": "1F473",
"unicode_alternates": [],
"name": "man with turban",
"shortname": ":man_with_turban:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -16862,13 +18912,16 @@
"indian",
"mummy",
"wisdom",
- "peace"
+ "peace",
+ "people",
+ "hat",
+ "diversity"
],
"moji": "👳"
},
"man_with_turban_tone1": {
"unicode": "1F473-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "man with turban tone 1",
"shortname": ":man_with_turban_tone1:",
"category": "people",
@@ -16884,11 +18937,12 @@
"mummy",
"wisdom",
"peace"
- ]
+ ],
+ "moji": "👳🏻"
},
"man_with_turban_tone2": {
"unicode": "1F473-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "man with turban tone 2",
"shortname": ":man_with_turban_tone2:",
"category": "people",
@@ -16904,11 +18958,12 @@
"mummy",
"wisdom",
"peace"
- ]
+ ],
+ "moji": "👳🏼"
},
"man_with_turban_tone3": {
"unicode": "1F473-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "man with turban tone 3",
"shortname": ":man_with_turban_tone3:",
"category": "people",
@@ -16924,11 +18979,12 @@
"mummy",
"wisdom",
"peace"
- ]
+ ],
+ "moji": "👳🏽"
},
"man_with_turban_tone4": {
"unicode": "1F473-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "man with turban tone 4",
"shortname": ":man_with_turban_tone4:",
"category": "people",
@@ -16944,11 +19000,12 @@
"mummy",
"wisdom",
"peace"
- ]
+ ],
+ "moji": "👳🏾"
},
"man_with_turban_tone5": {
"unicode": "1F473-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "man with turban tone 5",
"shortname": ":man_with_turban_tone5:",
"category": "people",
@@ -16964,19 +19021,22 @@
"mummy",
"wisdom",
"peace"
- ]
+ ],
+ "moji": "👳🏿"
},
"mans_shoe": {
"unicode": "1F45E",
"unicode_alternates": [],
"name": "mans shoe",
"shortname": ":mans_shoe:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"fashion",
- "male"
+ "male",
+ "shoe",
+ "accessories"
],
"moji": "👞"
},
@@ -16985,7 +19045,7 @@
"unicode_alternates": [],
"name": "world map",
"shortname": ":map:",
- "category": "travel_places",
+ "category": "objects",
"aliases": [
":world_map:"
],
@@ -16993,8 +19053,12 @@
"keywords": [
"atlas",
"earth",
- "cartography"
- ]
+ "cartography",
+ "travel",
+ "map",
+ "vacation"
+ ],
+ "moji": "🗺"
},
"maple_leaf": {
"unicode": "1F341",
@@ -17012,28 +19076,42 @@
"maple",
"leaf",
"syrup",
- "canada",
"tree"
],
"moji": "🍁"
},
+ "martial_arts_uniform": {
+ "unicode": "1F94B",
+ "unicode_alternates": [],
+ "name": "martial arts uniform",
+ "shortname": ":martial_arts_uniform:",
+ "category": "activity",
+ "aliases": [
+ ":karate_uniform:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🥋"
+ },
"mask": {
"unicode": "1F637",
"unicode_alternates": [],
"name": "face with medical mask",
"shortname": ":mask:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"face",
"ill",
"sick",
- "sick",
"virus",
"flu",
"medical",
- "mask"
+ "mask",
+ "smiley",
+ "dead",
+ "health"
],
"moji": "😷"
},
@@ -17042,19 +19120,22 @@
"unicode_alternates": [],
"name": "face massage",
"shortname": ":massage:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"female",
"girl",
- "woman"
+ "woman",
+ "people",
+ "women",
+ "diversity"
],
"moji": "💆"
},
"massage_tone1": {
"unicode": "1F486-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "face massage tone 1",
"shortname": ":massage_tone1:",
"category": "people",
@@ -17064,11 +19145,12 @@
"female",
"girl",
"woman"
- ]
+ ],
+ "moji": "💆🏻"
},
"massage_tone2": {
"unicode": "1F486-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "face massage tone 2",
"shortname": ":massage_tone2:",
"category": "people",
@@ -17078,11 +19160,12 @@
"female",
"girl",
"woman"
- ]
+ ],
+ "moji": "💆🏼"
},
"massage_tone3": {
"unicode": "1F486-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "face massage tone 3",
"shortname": ":massage_tone3:",
"category": "people",
@@ -17092,11 +19175,12 @@
"female",
"girl",
"woman"
- ]
+ ],
+ "moji": "💆🏽"
},
"massage_tone4": {
"unicode": "1F486-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "face massage tone 4",
"shortname": ":massage_tone4:",
"category": "people",
@@ -17106,11 +19190,12 @@
"female",
"girl",
"woman"
- ]
+ ],
+ "moji": "💆🏾"
},
"massage_tone5": {
"unicode": "1F486-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "face massage tone 5",
"shortname": ":massage_tone5:",
"category": "people",
@@ -17120,14 +19205,15 @@
"female",
"girl",
"woman"
- ]
+ ],
+ "moji": "💆🏿"
},
"meat_on_bone": {
"unicode": "1F356",
"unicode_alternates": [],
"name": "meat on bone",
"shortname": ":meat_on_bone:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -17160,21 +19246,27 @@
"first",
"show",
"reward",
- "achievement"
- ]
+ "achievement",
+ "object",
+ "sport",
+ "perfect"
+ ],
+ "moji": "🏅"
},
"mega": {
"unicode": "1F4E3",
"unicode_alternates": [],
"name": "cheering megaphone",
"shortname": ":mega:",
- "category": "objects",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"sound",
"speaker",
- "volume"
+ "volume",
+ "object",
+ "sport"
],
"moji": "📣"
},
@@ -17183,7 +19275,7 @@
"unicode_alternates": [],
"name": "melon",
"shortname": ":melon:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -17192,26 +19284,34 @@
"nature",
"melon",
"cantaloupe",
- "honeydew"
+ "honeydew",
+ "boobs"
],
"moji": "🍈"
},
"menorah": {
"unicode": "1F54E",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "menorah with nine branches",
"shortname": ":menorah:",
"category": "symbols",
"aliases": [],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "religion",
+ "object",
+ "jew",
+ "symbol",
+ "holidays"
+ ],
+ "moji": "🕎"
},
"mens": {
"unicode": "1F6B9",
"unicode_alternates": [],
"name": "mens symbol",
"shortname": ":mens:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -17220,17 +19320,17 @@
"wc",
"men",
"bathroom",
- "restroom",
"sign",
"boy",
"male",
- "avatar"
+ "avatar",
+ "symbol"
],
"moji": "🚹"
},
"metal": {
"unicode": "1F918",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "sign of the horns",
"shortname": ":metal:",
"category": "people",
@@ -17242,12 +19342,19 @@
"band",
"concert",
"fingers",
- "rocknroll"
- ]
+ "rocknroll",
+ "body",
+ "hands",
+ "hi",
+ "diversity",
+ "boys night",
+ "parties"
+ ],
+ "moji": "🤘"
},
"metal_tone1": {
"unicode": "1F918-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "sign of the horns tone 1",
"shortname": ":metal_tone1:",
"category": "people",
@@ -17260,11 +19367,12 @@
"concert",
"fingers",
"rocknroll"
- ]
+ ],
+ "moji": "🤘🏻"
},
"metal_tone2": {
"unicode": "1F918-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "sign of the horns tone 2",
"shortname": ":metal_tone2:",
"category": "people",
@@ -17277,11 +19385,12 @@
"concert",
"fingers",
"rocknroll"
- ]
+ ],
+ "moji": "🤘🏼"
},
"metal_tone3": {
"unicode": "1F918-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "sign of the horns tone 3",
"shortname": ":metal_tone3:",
"category": "people",
@@ -17294,11 +19403,12 @@
"concert",
"fingers",
"rocknroll"
- ]
+ ],
+ "moji": "🤘🏽"
},
"metal_tone4": {
"unicode": "1F918-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "sign of the horns tone 4",
"shortname": ":metal_tone4:",
"category": "people",
@@ -17311,11 +19421,12 @@
"concert",
"fingers",
"rocknroll"
- ]
+ ],
+ "moji": "🤘🏾"
},
"metal_tone5": {
"unicode": "1F918-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "sign of the horns tone 5",
"shortname": ":metal_tone5:",
"category": "people",
@@ -17328,14 +19439,15 @@
"concert",
"fingers",
"rocknroll"
- ]
+ ],
+ "moji": "🤘🏿"
},
"metro": {
"unicode": "1F687",
"unicode_alternates": [],
"name": "metro",
"shortname": ":metro:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -17346,8 +19458,8 @@
"underground",
"metro",
"subway",
- "underground",
- "train"
+ "train",
+ "travel"
],
"moji": "🚇"
},
@@ -17356,7 +19468,7 @@
"unicode_alternates": [],
"name": "microphone",
"shortname": ":microphone:",
- "category": "objects",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -17366,9 +19478,9 @@
"microphone",
"mic",
"audio",
- "sound",
"voice",
- "karaoke"
+ "karaoke",
+ "instruments"
],
"moji": "🎤"
},
@@ -17377,7 +19489,7 @@
"unicode_alternates": [],
"name": "studio microphone",
"shortname": ":microphone2:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [
":studio_microphone:"
],
@@ -17385,8 +19497,11 @@
"keywords": [
"mic",
"audio",
- "recording"
- ]
+ "recording",
+ "electronics",
+ "object"
+ ],
+ "moji": "🎙"
},
"microscope": {
"unicode": "1F52C",
@@ -17399,7 +19514,9 @@
"keywords": [
"experiment",
"laboratory",
- "zoomin"
+ "zoomin",
+ "object",
+ "science"
],
"moji": "🔬"
},
@@ -17414,12 +19531,17 @@
],
"aliases_ascii": [],
"keywords": [
- "fu"
- ]
+ "fu",
+ "body",
+ "hands",
+ "middle finger",
+ "diversity"
+ ],
+ "moji": "🖕"
},
"middle_finger_tone1": {
"unicode": "1F595-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "reversed hand with middle finger extended tone 1",
"shortname": ":middle_finger_tone1:",
"category": "people",
@@ -17429,11 +19551,12 @@
"aliases_ascii": [],
"keywords": [
"fu"
- ]
+ ],
+ "moji": "🖕🏻"
},
"middle_finger_tone2": {
"unicode": "1F595-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "reversed hand with middle finger extended tone 2",
"shortname": ":middle_finger_tone2:",
"category": "people",
@@ -17443,11 +19566,12 @@
"aliases_ascii": [],
"keywords": [
"fu"
- ]
+ ],
+ "moji": "🖕🏼"
},
"middle_finger_tone3": {
"unicode": "1F595-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "reversed hand with middle finger extended tone 3",
"shortname": ":middle_finger_tone3:",
"category": "people",
@@ -17457,11 +19581,12 @@
"aliases_ascii": [],
"keywords": [
"fu"
- ]
+ ],
+ "moji": "🖕🏽"
},
"middle_finger_tone4": {
"unicode": "1F595-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "reversed hand with middle finger extended tone 4",
"shortname": ":middle_finger_tone4:",
"category": "people",
@@ -17471,11 +19596,12 @@
"aliases_ascii": [],
"keywords": [
"fu"
- ]
+ ],
+ "moji": "🖕🏾"
},
"middle_finger_tone5": {
"unicode": "1F595-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "reversed hand with middle finger extended tone 5",
"shortname": ":middle_finger_tone5:",
"category": "people",
@@ -17485,14 +19611,15 @@
"aliases_ascii": [],
"keywords": [
"fu"
- ]
+ ],
+ "moji": "🖕🏿"
},
"military_medal": {
"unicode": "1F396",
"unicode_alternates": [],
"name": "military medal",
"shortname": ":military_medal:",
- "category": "celebration",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -17500,15 +19627,32 @@
"acknowledgment",
"purple heart",
"heroism",
- "veteran"
- ]
+ "veteran",
+ "object",
+ "award",
+ "win"
+ ],
+ "moji": "🎖"
+ },
+ "milk": {
+ "unicode": "1F95B",
+ "unicode_alternates": [],
+ "name": "glass of milk",
+ "shortname": ":milk:",
+ "category": "food",
+ "aliases": [
+ ":glass_of_milk:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🥛"
},
"milky_way": {
"unicode": "1F30C",
"unicode_alternates": [],
"name": "milky way",
"shortname": ":milky_way:",
- "category": "nature",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -17519,8 +19663,10 @@
"star",
"stars",
"planets",
- "space",
- "sky"
+ "sky",
+ "places",
+ "travel",
+ "vacation"
],
"moji": "🌌"
},
@@ -17529,7 +19675,7 @@
"unicode_alternates": [],
"name": "minibus",
"shortname": ":minibus:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -17538,8 +19684,7 @@
"vehicle",
"bus",
"city",
- "transport",
- "transportation"
+ "transport"
],
"moji": "🚐"
},
@@ -17556,7 +19701,8 @@
"disc",
"disk",
"record",
- "technology"
+ "technology",
+ "electronics"
],
"moji": "💽"
},
@@ -17565,17 +19711,18 @@
"unicode_alternates": [],
"name": "mobile phone off",
"shortname": ":mobile_phone_off:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "mute"
+ "mute",
+ "symbol"
],
"moji": "📴"
},
"money_mouth": {
"unicode": "1F911",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "money-mouth face",
"shortname": ":money_mouth:",
"category": "people",
@@ -17583,7 +19730,14 @@
":money_mouth_face:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "smiley",
+ "win",
+ "money",
+ "emotion",
+ "boys night"
+ ],
+ "moji": "🤑"
},
"money_with_wings": {
"unicode": "1F4B8",
@@ -17607,7 +19761,7 @@
"burned",
"gift",
"cash",
- "dollar"
+ "boys night"
],
"moji": "💸"
},
@@ -17622,7 +19776,10 @@
"keywords": [
"coins",
"dollar",
- "payment"
+ "payment",
+ "bag",
+ "award",
+ "money"
],
"moji": "💰"
},
@@ -17640,7 +19797,8 @@
"monkey",
"primate",
"banana",
- "silly"
+ "silly",
+ "wildlife"
],
"moji": "🐒"
},
@@ -17663,7 +19821,7 @@
"unicode_alternates": [],
"name": "monorail",
"shortname": ":monorail:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -17672,66 +19830,18 @@
"train",
"mono",
"rail",
- "transport"
+ "transport",
+ "travel",
+ "vacation"
],
"moji": "🚝"
},
- "mood_bubble": {
- "unicode": "1F5F0",
- "unicode_alternates": [],
- "name": "mood bubble",
- "shortname": ":mood_bubble:",
- "category": "objects_symbols",
- "aliases": [],
- "aliases_ascii": [],
- "keywords": [
- "balloon",
- "conversation",
- "communication",
- "comic",
- "feeling"
- ]
- },
- "mood_bubble_lightning": {
- "unicode": "1F5F1",
- "unicode_alternates": [],
- "name": "lightning mood bubble",
- "shortname": ":mood_bubble_lightning:",
- "category": "objects_symbols",
- "aliases": [
- ":lightning_mood_bubble:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "balloon",
- "conversation",
- "communication",
- "comic",
- "feeling"
- ]
- },
- "mood_lightning": {
- "unicode": "1F5F2",
- "unicode_alternates": [],
- "name": "lightning mood",
- "shortname": ":mood_lightning:",
- "category": "objects_symbols",
- "aliases": [
- ":lightning_mood:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "zap",
- "electric",
- "current"
- ]
- },
"mortar_board": {
"unicode": "1F393",
"unicode_alternates": [],
"name": "graduation cap",
"shortname": ":mortar_board:",
- "category": "objects",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -17742,33 +19852,55 @@
"hat",
"school",
"university",
- "graduation",
- "cap",
"mortarboard",
"academic",
"education",
"ceremony",
"square",
- "tassel"
+ "tassel",
+ "office",
+ "accessories"
],
"moji": "🎓"
},
"mosque": {
"unicode": "1F54C",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "mosque",
"shortname": ":mosque:",
"category": "travel",
"aliases": [],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "places",
+ "religion",
+ "building",
+ "vacation",
+ "condolence"
+ ],
+ "moji": "🕌"
+ },
+ "motor_scooter": {
+ "unicode": "1F6F5",
+ "unicode_alternates": [],
+ "name": "motor scooter",
+ "shortname": ":motor_scooter:",
+ "category": "travel",
+ "aliases": [
+ ":motorbike:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "moped"
+ ],
+ "moji": "🛵"
},
"motorboat": {
"unicode": "1F6E5",
"unicode_alternates": [],
"name": "motorboat",
"shortname": ":motorboat:",
- "category": "travel_places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -17776,30 +19908,35 @@
"vehicle",
"boat",
"speedboat",
- "powerboat"
- ]
+ "powerboat",
+ "travel"
+ ],
+ "moji": "🛥"
},
"motorcycle": {
"unicode": "1F3CD",
"unicode_alternates": [],
"name": "racing motorcycle",
"shortname": ":motorcycle:",
- "category": "activity",
+ "category": "travel",
"aliases": [
":racing_motorcycle:"
],
"aliases_ascii": [],
"keywords": [
"bike",
- "speed"
- ]
+ "speed",
+ "transportation",
+ "travel"
+ ],
+ "moji": "🏍"
},
"motorway": {
"unicode": "1F6E3",
"unicode_alternates": [],
"name": "motorway",
"shortname": ":motorway:",
- "category": "travel_places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -17807,43 +19944,56 @@
"highway",
"freeway",
"traffic",
- "travel"
- ]
+ "travel",
+ "vacation",
+ "camp"
+ ],
+ "moji": "🛣"
},
"mount_fuji": {
"unicode": "1F5FB",
"unicode_alternates": [],
"name": "mount fuji",
"shortname": ":mount_fuji:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"japan",
"mountain",
"nature",
- "photo"
+ "photo",
+ "places",
+ "travel",
+ "vacation",
+ "cold",
+ "camp"
],
"moji": "🗻"
},
"mountain": {
"unicode": "26F0",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "mountain",
"shortname": ":mountain:",
"category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "place"
- ]
+ "place",
+ "places",
+ "travel",
+ "vacation",
+ "camp"
+ ],
+ "moji": "⛰"
},
"mountain_bicyclist": {
"unicode": "1F6B5",
"unicode_alternates": [],
"name": "mountain bicyclist",
"shortname": ":mountain_bicyclist:",
- "category": "objects",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -17855,13 +20005,15 @@
"bike",
"pedal",
"bicycle",
- "transportation"
+ "men",
+ "sport",
+ "diversity"
],
"moji": "🚵"
},
"mountain_bicyclist_tone1": {
"unicode": "1F6B5-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "mountain bicyclist tone 1",
"shortname": ":mountain_bicyclist_tone1:",
"category": "activity",
@@ -17872,13 +20024,13 @@
"transportation",
"bike",
"pedal",
- "bicycle",
- "transportation"
- ]
+ "bicycle"
+ ],
+ "moji": "🚵🏻"
},
"mountain_bicyclist_tone2": {
"unicode": "1F6B5-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "mountain bicyclist tone 2",
"shortname": ":mountain_bicyclist_tone2:",
"category": "activity",
@@ -17889,13 +20041,13 @@
"transportation",
"bike",
"pedal",
- "bicycle",
- "transportation"
- ]
+ "bicycle"
+ ],
+ "moji": "🚵🏼"
},
"mountain_bicyclist_tone3": {
"unicode": "1F6B5-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "mountain bicyclist tone 3",
"shortname": ":mountain_bicyclist_tone3:",
"category": "activity",
@@ -17906,13 +20058,13 @@
"transportation",
"bike",
"pedal",
- "bicycle",
- "transportation"
- ]
+ "bicycle"
+ ],
+ "moji": "🚵🏽"
},
"mountain_bicyclist_tone4": {
"unicode": "1F6B5-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "mountain bicyclist tone 4",
"shortname": ":mountain_bicyclist_tone4:",
"category": "activity",
@@ -17923,13 +20075,13 @@
"transportation",
"bike",
"pedal",
- "bicycle",
- "transportation"
- ]
+ "bicycle"
+ ],
+ "moji": "🚵🏾"
},
"mountain_bicyclist_tone5": {
"unicode": "1F6B5-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "mountain bicyclist tone 5",
"shortname": ":mountain_bicyclist_tone5:",
"category": "activity",
@@ -17940,16 +20092,16 @@
"transportation",
"bike",
"pedal",
- "bicycle",
- "transportation"
- ]
+ "bicycle"
+ ],
+ "moji": "🚵🏿"
},
"mountain_cableway": {
"unicode": "1F6A0",
"unicode_alternates": [],
"name": "mountain cableway",
"shortname": ":mountain_cableway:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -17959,7 +20111,8 @@
"cable",
"rail",
"train",
- "railway"
+ "railway",
+ "travel"
],
"moji": "🚠"
},
@@ -17968,7 +20121,7 @@
"unicode_alternates": [],
"name": "mountain railway",
"shortname": ":mountain_railway:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -17977,7 +20130,8 @@
"railway",
"rail",
"train",
- "transport"
+ "transport",
+ "travel"
],
"moji": "🚞"
},
@@ -17986,7 +20140,7 @@
"unicode_alternates": [],
"name": "snow capped mountain",
"shortname": ":mountain_snow:",
- "category": "travel_places",
+ "category": "travel",
"aliases": [
":snow_capped_mountain:"
],
@@ -17995,8 +20149,13 @@
"cold",
"elevation",
"hiking",
- "peak"
- ]
+ "peak",
+ "places",
+ "travel",
+ "vacation",
+ "camp"
+ ],
+ "moji": "🏔"
},
"mouse": {
"unicode": "1F42D",
@@ -18029,25 +20188,9 @@
],
"moji": "🐁"
},
- "mouse_one": {
- "unicode": "1F5AF",
- "unicode_alternates": [],
- "name": "one button mouse",
- "shortname": ":mouse_one:",
- "category": "objects_symbols",
- "aliases": [
- ":one_button_mouse:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "computer",
- "input",
- "device"
- ]
- },
"mouse_three_button": {
"unicode": "1F5B1",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "three button mouse",
"shortname": ":mouse_three_button:",
"category": "objects",
@@ -18059,8 +20202,12 @@
"3",
"computer",
"object",
- "office"
- ]
+ "office",
+ "electronics",
+ "work",
+ "game"
+ ],
+ "moji": "🖱"
},
"movie_camera": {
"unicode": "1F3A5",
@@ -18078,7 +20225,8 @@
"camcorder",
"video",
"motion",
- "picture"
+ "picture",
+ "object"
],
"moji": "🎥"
},
@@ -18087,21 +20235,101 @@
"unicode_alternates": [],
"name": "moyai",
"shortname": ":moyai:",
- "category": "places",
+ "category": "objects",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"island",
- "stone"
+ "stone",
+ "travel",
+ "vacation"
],
"moji": "🗿"
},
+ "mrs_claus": {
+ "unicode": "1F936",
+ "unicode_alternates": [],
+ "name": "mother christmas",
+ "shortname": ":mrs_claus:",
+ "category": "people",
+ "aliases": [
+ ":mother_christmas:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤶"
+ },
+ "mrs_claus_tone1": {
+ "unicode": "1F936-1F3FB",
+ "unicode_alternates": [],
+ "name": "mother christmas tone 1",
+ "shortname": ":mrs_claus_tone1:",
+ "category": "people",
+ "aliases": [
+ ":mother_christmas_tone1:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤶🏻"
+ },
+ "mrs_claus_tone2": {
+ "unicode": "1F936-1F3FC",
+ "unicode_alternates": [],
+ "name": "mother christmas tone 2",
+ "shortname": ":mrs_claus_tone2:",
+ "category": "people",
+ "aliases": [
+ ":mother_christmas_tone2:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤶🏼"
+ },
+ "mrs_claus_tone3": {
+ "unicode": "1F936-1F3FD",
+ "unicode_alternates": [],
+ "name": "mother christmas tone 3",
+ "shortname": ":mrs_claus_tone3:",
+ "category": "people",
+ "aliases": [
+ ":mother_christmas_tone3:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤶🏽"
+ },
+ "mrs_claus_tone4": {
+ "unicode": "1F936-1F3FE",
+ "unicode_alternates": [],
+ "name": "mother christmas tone 4",
+ "shortname": ":mrs_claus_tone4:",
+ "category": "people",
+ "aliases": [
+ ":mother_christmas_tone4:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤶🏾"
+ },
+ "mrs_claus_tone5": {
+ "unicode": "1F936-1F3FF",
+ "unicode_alternates": [],
+ "name": "mother christmas tone 5",
+ "shortname": ":mrs_claus_tone5:",
+ "category": "people",
+ "aliases": [
+ ":mother_christmas_tone5:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤶🏿"
+ },
"muscle": {
"unicode": "1F4AA",
"unicode_alternates": [],
"name": "flexed biceps",
"shortname": ":muscle:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -18110,13 +20338,20 @@
"hand",
"strong",
"muscle",
- "bicep"
+ "bicep",
+ "body",
+ "hands",
+ "workout",
+ "win",
+ "diversity",
+ "feminist",
+ "boys night"
],
"moji": "💪"
},
"muscle_tone1": {
"unicode": "1F4AA-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "flexed biceps tone 1",
"shortname": ":muscle_tone1:",
"category": "people",
@@ -18129,11 +20364,12 @@
"strong",
"muscle",
"bicep"
- ]
+ ],
+ "moji": "💪🏻"
},
"muscle_tone2": {
"unicode": "1F4AA-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "flexed biceps tone 2",
"shortname": ":muscle_tone2:",
"category": "people",
@@ -18146,11 +20382,12 @@
"strong",
"muscle",
"bicep"
- ]
+ ],
+ "moji": "💪🏼"
},
"muscle_tone3": {
"unicode": "1F4AA-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "flexed biceps tone 3",
"shortname": ":muscle_tone3:",
"category": "people",
@@ -18163,11 +20400,12 @@
"strong",
"muscle",
"bicep"
- ]
+ ],
+ "moji": "💪🏽"
},
"muscle_tone4": {
"unicode": "1F4AA-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "flexed biceps tone 4",
"shortname": ":muscle_tone4:",
"category": "people",
@@ -18180,11 +20418,12 @@
"strong",
"muscle",
"bicep"
- ]
+ ],
+ "moji": "💪🏾"
},
"muscle_tone5": {
"unicode": "1F4AA-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "flexed biceps tone 5",
"shortname": ":muscle_tone5:",
"category": "people",
@@ -18197,7 +20436,8 @@
"strong",
"muscle",
"bicep"
- ]
+ ],
+ "moji": "💪🏿"
},
"mushroom": {
"unicode": "1F344",
@@ -18213,7 +20453,9 @@
"mushroom",
"fungi",
"food",
- "fungus"
+ "fungus",
+ "nature",
+ "drugs"
],
"moji": "🍄"
},
@@ -18222,7 +20464,7 @@
"unicode_alternates": [],
"name": "musical keyboard",
"shortname": ":musical_keyboard:",
- "category": "objects",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -18230,10 +20472,9 @@
"piano",
"music",
"keyboard",
- "piano",
"organ",
- "instrument",
- "electric"
+ "electric",
+ "instruments"
],
"moji": "🎹"
},
@@ -18242,7 +20483,7 @@
"unicode_alternates": [],
"name": "musical note",
"shortname": ":musical_note:",
- "category": "objects",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -18250,8 +20491,9 @@
"musical",
"music",
"note",
- "music",
- "sound"
+ "sound",
+ "instruments",
+ "symbol"
],
"moji": "🎵"
},
@@ -18260,7 +20502,7 @@
"unicode_alternates": [],
"name": "musical score",
"shortname": ":musical_score:",
- "category": "objects",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -18269,10 +20511,10 @@
"music",
"musical",
"score",
- "clef",
"g-clef",
"stave",
- "staff"
+ "staff",
+ "instruments"
],
"moji": "🎼"
},
@@ -18281,12 +20523,14 @@
"unicode_alternates": [],
"name": "speaker with cancellation stroke",
"shortname": ":mute:",
- "category": "objects",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"sound",
- "volume"
+ "volume",
+ "alarm",
+ "symbol"
],
"moji": "🔇"
},
@@ -18295,18 +20539,24 @@
"unicode_alternates": [],
"name": "nail polish",
"shortname": ":nail_care:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"beauty",
- "manicure"
+ "manicure",
+ "women",
+ "body",
+ "hands",
+ "nailpolish",
+ "diversity",
+ "girls night"
],
"moji": "💅"
},
"nail_care_tone1": {
"unicode": "1F485-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "nail polish tone 1",
"shortname": ":nail_care_tone1:",
"category": "people",
@@ -18315,11 +20565,12 @@
"keywords": [
"beauty",
"manicure"
- ]
+ ],
+ "moji": "💅🏻"
},
"nail_care_tone2": {
"unicode": "1F485-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "nail polish tone 2",
"shortname": ":nail_care_tone2:",
"category": "people",
@@ -18328,11 +20579,12 @@
"keywords": [
"beauty",
"manicure"
- ]
+ ],
+ "moji": "💅🏼"
},
"nail_care_tone3": {
"unicode": "1F485-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "nail polish tone 3",
"shortname": ":nail_care_tone3:",
"category": "people",
@@ -18341,11 +20593,12 @@
"keywords": [
"beauty",
"manicure"
- ]
+ ],
+ "moji": "💅🏽"
},
"nail_care_tone4": {
"unicode": "1F485-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "nail polish tone 4",
"shortname": ":nail_care_tone4:",
"category": "people",
@@ -18354,11 +20607,12 @@
"keywords": [
"beauty",
"manicure"
- ]
+ ],
+ "moji": "💅🏾"
},
"nail_care_tone5": {
"unicode": "1F485-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "nail polish tone 5",
"shortname": ":nail_care_tone5:",
"category": "people",
@@ -18367,28 +20621,43 @@
"keywords": [
"beauty",
"manicure"
- ]
+ ],
+ "moji": "💅🏿"
},
"name_badge": {
"unicode": "1F4DB",
"unicode_alternates": [],
"name": "name badge",
"shortname": ":name_badge:",
- "category": "objects",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"fire",
- "forbid"
+ "forbid",
+ "work"
],
"moji": "📛"
},
+ "nauseated_face": {
+ "unicode": "1F922",
+ "unicode_alternates": [],
+ "name": "nauseated face",
+ "shortname": ":nauseated_face:",
+ "category": "people",
+ "aliases": [
+ ":sick:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤢"
+ },
"necktie": {
"unicode": "1F454",
"unicode_alternates": [],
"name": "necktie",
"shortname": ":necktie:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -18405,20 +20674,21 @@
"unicode_alternates": [],
"name": "negative squared cross mark",
"shortname": ":negative_squared_cross_mark:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"deny",
"green-square",
"no",
- "x"
+ "x",
+ "symbol"
],
"moji": "❎"
},
"nerd": {
"unicode": "1F913",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "nerd face",
"shortname": ":nerd:",
"category": "people",
@@ -18426,31 +20696,18 @@
":nerd_face:"
],
"aliases_ascii": [],
- "keywords": []
- },
- "network": {
- "unicode": "1F5A7",
- "unicode_alternates": [],
- "name": "three networked computers",
- "shortname": ":network:",
- "category": "objects_symbols",
- "aliases": [
- ":three_networked_computers:"
- ],
- "aliases_ascii": [],
"keywords": [
- "lan",
- "wan",
- "network",
- "technology"
- ]
+ "smiley",
+ "glasses"
+ ],
+ "moji": "🤓"
},
"neutral_face": {
"unicode": "1F610",
"unicode_alternates": [],
"name": "neutral face",
"shortname": ":neutral_face:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -18459,7 +20716,11 @@
"neutral",
"objective",
"impartial",
- "blank"
+ "blank",
+ "mad",
+ "smiley",
+ "shrug",
+ "emotion"
],
"moji": "😐"
},
@@ -18468,11 +20729,12 @@
"unicode_alternates": [],
"name": "squared new",
"shortname": ":new:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "blue-square"
+ "blue-square",
+ "symbol"
],
"moji": "🆕"
},
@@ -18491,7 +20753,8 @@
"sky",
"night",
"cheese",
- "phase"
+ "phase",
+ "space"
],
"moji": "🌑"
},
@@ -18512,7 +20775,9 @@
"sky",
"night",
"cheese",
- "phase"
+ "phase",
+ "space",
+ "goodnight"
],
"moji": "🌚"
},
@@ -18526,7 +20791,9 @@
"aliases_ascii": [],
"keywords": [
"headline",
- "press"
+ "press",
+ "office",
+ "write"
],
"moji": "📰"
},
@@ -18535,19 +20802,22 @@
"unicode_alternates": [],
"name": "rolled-up newspaper",
"shortname": ":newspaper2:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [
":rolled_up_newspaper:"
],
"aliases_ascii": [],
"keywords": [
"headline",
- "press"
- ]
+ "press",
+ "office",
+ "write"
+ ],
+ "moji": "🗞"
},
"ng": {
"unicode": "1F196",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "squared ng",
"shortname": ":ng:",
"category": "symbols",
@@ -18558,14 +20828,15 @@
"no good",
"symbol",
"word"
- ]
+ ],
+ "moji": "🆖"
},
"night_with_stars": {
"unicode": "1F303",
"unicode_alternates": [],
"name": "night with stars",
"shortname": ":night_with_stars:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -18575,7 +20846,11 @@
"evening",
"planets",
"space",
- "sky"
+ "sky",
+ "places",
+ "building",
+ "vacation",
+ "goodnight"
],
"moji": "🌃"
},
@@ -18585,15 +20860,18 @@
"unicode_alternates": [
"0039-FE0F-20E3"
],
- "name": "digit nine",
+ "name": "keycap digit nine",
"shortname": ":nine:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"9",
"blue-square",
- "numbers"
+ "numbers",
+ "number",
+ "math",
+ "symbol"
]
},
"no_bell": {
@@ -18601,13 +20879,15 @@
"unicode_alternates": [],
"name": "bell with cancellation stroke",
"shortname": ":no_bell:",
- "category": "objects",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"mute",
"sound",
- "volume"
+ "volume",
+ "alarm",
+ "symbol"
],
"moji": "🔕"
},
@@ -18616,7 +20896,7 @@
"unicode_alternates": [],
"name": "no bicycles",
"shortname": ":no_bicycles:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -18624,7 +20904,8 @@
"prohibited",
"bicycle",
"bike pedal",
- "no"
+ "no",
+ "symbol"
],
"moji": "🚳"
},
@@ -18635,7 +20916,7 @@
],
"name": "no entry",
"shortname": ":no_entry:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -18644,7 +20925,9 @@
"limit",
"privacy",
"security",
- "stop"
+ "stop",
+ "symbol",
+ "circle"
],
"moji": "⛔"
},
@@ -18653,7 +20936,7 @@
"unicode_alternates": [],
"name": "no entry sign",
"shortname": ":no_entry_sign:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -18663,8 +20946,9 @@
"limit",
"stop",
"no",
- "stop",
- "entry"
+ "entry",
+ "symbol",
+ "circle"
],
"moji": "🚫"
},
@@ -18673,7 +20957,7 @@
"unicode_alternates": [],
"name": "face with no good gesture",
"shortname": ":no_good:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -18684,13 +20968,17 @@
"stop",
"nope",
"don&#039;t",
- "not"
+ "not",
+ "people",
+ "women",
+ "diversity",
+ "girls night"
],
"moji": "🙅"
},
"no_good_tone1": {
"unicode": "1F645-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "face with no good gesture tone 1",
"shortname": ":no_good_tone1:",
"category": "people",
@@ -18708,11 +20996,12 @@
"hand",
"person",
"prohibited"
- ]
+ ],
+ "moji": "🙅🏻"
},
"no_good_tone2": {
"unicode": "1F645-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "face with no good gesture tone 2",
"shortname": ":no_good_tone2:",
"category": "people",
@@ -18730,11 +21019,12 @@
"hand",
"person",
"prohibited"
- ]
+ ],
+ "moji": "🙅🏼"
},
"no_good_tone3": {
"unicode": "1F645-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "face with no good gesture tone 3",
"shortname": ":no_good_tone3:",
"category": "people",
@@ -18752,11 +21042,12 @@
"hand",
"person",
"prohibited"
- ]
+ ],
+ "moji": "🙅🏽"
},
"no_good_tone4": {
"unicode": "1F645-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "face with no good gesture tone 4",
"shortname": ":no_good_tone4:",
"category": "people",
@@ -18774,11 +21065,12 @@
"hand",
"person",
"prohibited"
- ]
+ ],
+ "moji": "🙅🏾"
},
"no_good_tone5": {
"unicode": "1F645-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "face with no good gesture tone 5",
"shortname": ":no_good_tone5:",
"category": "people",
@@ -18796,19 +21088,22 @@
"hand",
"person",
"prohibited"
- ]
+ ],
+ "moji": "🙅🏿"
},
"no_mobile_phones": {
"unicode": "1F4F5",
"unicode_alternates": [],
"name": "no mobile phones",
"shortname": ":no_mobile_phones:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"iphone",
- "mute"
+ "mute",
+ "symbol",
+ "phone"
],
"moji": "📵"
},
@@ -18817,7 +21112,7 @@
"unicode_alternates": [],
"name": "face without mouth",
"shortname": ":no_mouth:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [
":-X",
@@ -18835,7 +21130,11 @@
"hellokitty",
"mouth",
"silent",
- "vapid"
+ "vapid",
+ "mad",
+ "smiley",
+ "neutral",
+ "emotion"
],
"moji": "😶"
},
@@ -18844,7 +21143,7 @@
"unicode_alternates": [],
"name": "no pedestrians",
"shortname": ":no_pedestrians:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -18857,7 +21156,8 @@
"stroll",
"stride",
"foot",
- "feet"
+ "feet",
+ "symbol"
],
"moji": "🚷"
},
@@ -18866,20 +21166,20 @@
"unicode_alternates": [],
"name": "no smoking symbol",
"shortname": ":no_smoking:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"cigarette",
"no",
"smoking",
- "cigarette",
"smoke",
"cancer",
"lungs",
"inhale",
"tar",
- "nicotine"
+ "nicotine",
+ "symbol"
],
"moji": "🚭"
},
@@ -18888,7 +21188,7 @@
"unicode_alternates": [],
"name": "non-potable water symbol",
"shortname": ":non-potable_water:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -18901,7 +21201,8 @@
"dirty",
"gross",
"aqua",
- "h20"
+ "h20",
+ "symbol"
],
"moji": "🚱"
},
@@ -18910,18 +21211,20 @@
"unicode_alternates": [],
"name": "nose",
"shortname": ":nose:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"smell",
- "sniff"
+ "sniff",
+ "body",
+ "diversity"
],
"moji": "👃"
},
"nose_tone1": {
"unicode": "1F443-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "nose tone 1",
"shortname": ":nose_tone1:",
"category": "people",
@@ -18930,11 +21233,12 @@
"keywords": [
"smell",
"sniff"
- ]
+ ],
+ "moji": "👃🏻"
},
"nose_tone2": {
"unicode": "1F443-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "nose tone 2",
"shortname": ":nose_tone2:",
"category": "people",
@@ -18943,11 +21247,12 @@
"keywords": [
"smell",
"sniff"
- ]
+ ],
+ "moji": "👃🏼"
},
"nose_tone3": {
"unicode": "1F443-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "nose tone 3",
"shortname": ":nose_tone3:",
"category": "people",
@@ -18956,11 +21261,12 @@
"keywords": [
"smell",
"sniff"
- ]
+ ],
+ "moji": "👃🏽"
},
"nose_tone4": {
"unicode": "1F443-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "nose tone 4",
"shortname": ":nose_tone4:",
"category": "people",
@@ -18969,11 +21275,12 @@
"keywords": [
"smell",
"sniff"
- ]
+ ],
+ "moji": "👃🏾"
},
"nose_tone5": {
"unicode": "1F443-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "nose tone 5",
"shortname": ":nose_tone5:",
"category": "people",
@@ -18982,37 +21289,8 @@
"keywords": [
"smell",
"sniff"
- ]
- },
- "note": {
- "unicode": "1F5C9",
- "unicode_alternates": [],
- "name": "note page",
- "shortname": ":note:",
- "category": "objects_symbols",
- "aliases": [
- ":note_page:"
],
- "aliases_ascii": [],
- "keywords": [
- "stationery",
- "post-it"
- ]
- },
- "note_empty": {
- "unicode": "1F5C6",
- "unicode_alternates": [],
- "name": "empty note page",
- "shortname": ":note_empty:",
- "category": "objects_symbols",
- "aliases": [
- ":empty_note_page:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "stationery",
- "post-it"
- ]
+ "moji": "👃🏿"
},
"notebook": {
"unicode": "1F4D3",
@@ -19026,7 +21304,10 @@
"notes",
"paper",
"record",
- "stationery"
+ "stationery",
+ "object",
+ "office",
+ "write"
],
"moji": "📓"
},
@@ -19042,71 +21323,48 @@
"classroom",
"notes",
"paper",
- "record"
+ "record",
+ "object",
+ "office",
+ "write"
],
"moji": "📔"
},
- "notepad": {
- "unicode": "1F5CA",
- "unicode_alternates": [],
- "name": "note pad",
- "shortname": ":notepad:",
- "category": "objects_symbols",
- "aliases": [
- ":note_pad:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "stationery",
- "post-it"
- ]
- },
- "notepad_empty": {
- "unicode": "1F5C7",
- "unicode_alternates": [],
- "name": "empty note pad",
- "shortname": ":notepad_empty:",
- "category": "objects_symbols",
- "aliases": [
- ":empty_note_pad:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "stationery",
- "post-it"
- ]
- },
"notepad_spiral": {
"unicode": "1F5D2",
"unicode_alternates": [],
"name": "spiral note pad",
"shortname": ":notepad_spiral:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [
":spiral_note_pad:"
],
"aliases_ascii": [],
"keywords": [
- "stationery"
- ]
+ "stationery",
+ "work",
+ "office",
+ "write"
+ ],
+ "moji": "🗒"
},
"notes": {
"unicode": "1F3B6",
"unicode_alternates": [],
"name": "multiple musical notes",
"shortname": ":notes:",
- "category": "objects",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"music",
"score",
"musical",
- "music",
"notes",
- "music",
"sound",
- "melody"
+ "melody",
+ "instruments",
+ "symbol"
],
"moji": "🎶"
},
@@ -19120,7 +21378,10 @@
"aliases_ascii": [],
"keywords": [
"handy",
- "tools"
+ "tools",
+ "object",
+ "tool",
+ "nutcase"
],
"moji": "🔩"
},
@@ -19131,12 +21392,13 @@
],
"name": "heavy large circle",
"shortname": ":o:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"circle",
- "round"
+ "round",
+ "symbol"
],
"moji": "⭕"
},
@@ -19145,13 +21407,14 @@
"unicode_alternates": [],
"name": "negative squared latin capital letter o",
"shortname": ":o2:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"alphabet",
"letter",
- "red-square"
+ "red-square",
+ "symbol"
],
"moji": "🅾"
},
@@ -19168,13 +21431,29 @@
"water",
"wave",
"ocean",
- "wave",
"surf",
"beach",
- "tide"
+ "tide",
+ "weather",
+ "boat",
+ "tropical",
+ "swim"
],
"moji": "🌊"
},
+ "octagonal_sign": {
+ "unicode": "1F6D1",
+ "unicode_alternates": [],
+ "name": "octagonal sign",
+ "shortname": ":octagonal_sign:",
+ "category": "symbols",
+ "aliases": [
+ ":stop_sign:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🛑"
+ },
"octopus": {
"unicode": "1F419",
"unicode_alternates": [],
@@ -19187,7 +21466,8 @@
"animal",
"creature",
"ocean",
- "sea"
+ "sea",
+ "wildlife"
],
"moji": "🐙"
},
@@ -19196,7 +21476,7 @@
"unicode_alternates": [],
"name": "oden",
"shortname": ":oden:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -19214,13 +21494,14 @@
"unicode_alternates": [],
"name": "office building",
"shortname": ":office:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"building",
"bureau",
- "work"
+ "work",
+ "places"
],
"moji": "🏢"
},
@@ -19229,28 +21510,31 @@
"unicode_alternates": [],
"name": "oil drum",
"shortname": ":oil:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [
":oil_drum:"
],
"aliases_ascii": [],
"keywords": [
- "petroleum"
- ]
+ "petroleum",
+ "object"
+ ],
+ "moji": "🛢"
},
"ok": {
"unicode": "1F197",
"unicode_alternates": [],
"name": "squared ok",
"shortname": ":ok:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"agree",
"blue-square",
"good",
- "yes"
+ "yes",
+ "symbol"
],
"moji": "🆗"
},
@@ -19259,7 +21543,7 @@
"unicode_alternates": [],
"name": "ok hand sign",
"shortname": ":ok_hand:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -19273,13 +21557,19 @@
"marijuana",
"joint",
"pot",
- "420"
+ "420",
+ "body",
+ "hands",
+ "hi",
+ "diversity",
+ "good",
+ "beautiful"
],
"moji": "👌"
},
"ok_hand_tone1": {
"unicode": "1F44C-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "ok hand sign tone 1",
"shortname": ":ok_hand_tone1:",
"category": "people",
@@ -19296,11 +21586,12 @@
"joint",
"pot",
"420"
- ]
+ ],
+ "moji": "👌🏻"
},
"ok_hand_tone2": {
"unicode": "1F44C-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "ok hand sign tone 2",
"shortname": ":ok_hand_tone2:",
"category": "people",
@@ -19317,11 +21608,12 @@
"joint",
"pot",
"420"
- ]
+ ],
+ "moji": "👌🏼"
},
"ok_hand_tone3": {
"unicode": "1F44C-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "ok hand sign tone 3",
"shortname": ":ok_hand_tone3:",
"category": "people",
@@ -19338,11 +21630,12 @@
"joint",
"pot",
"420"
- ]
+ ],
+ "moji": "👌🏽"
},
"ok_hand_tone4": {
"unicode": "1F44C-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "ok hand sign tone 4",
"shortname": ":ok_hand_tone4:",
"category": "people",
@@ -19359,11 +21652,12 @@
"joint",
"pot",
"420"
- ]
+ ],
+ "moji": "👌🏾"
},
"ok_hand_tone5": {
"unicode": "1F44C-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "ok hand sign tone 5",
"shortname": ":ok_hand_tone5:",
"category": "people",
@@ -19380,14 +21674,15 @@
"joint",
"pot",
"420"
- ]
+ ],
+ "moji": "👌🏿"
},
"ok_woman": {
"unicode": "1F646",
"unicode_alternates": [],
"name": "face with ok gesture",
"shortname": ":ok_woman:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [
"*\\0/*",
@@ -19404,13 +21699,15 @@
"yes",
"ok",
"okay",
- "accept"
+ "accept",
+ "people",
+ "diversity"
],
"moji": "🙆"
},
"ok_woman_tone1": {
"unicode": "1F646-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "face with ok gesture tone1",
"shortname": ":ok_woman_tone1:",
"category": "people",
@@ -19425,11 +21722,12 @@
"yes",
"okay",
"accept"
- ]
+ ],
+ "moji": "🙆🏻"
},
"ok_woman_tone2": {
"unicode": "1F646-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "face with ok gesture tone2",
"shortname": ":ok_woman_tone2:",
"category": "people",
@@ -19444,11 +21742,12 @@
"yes",
"okay",
"accept"
- ]
+ ],
+ "moji": "🙆🏼"
},
"ok_woman_tone3": {
"unicode": "1F646-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "face with ok gesture tone3",
"shortname": ":ok_woman_tone3:",
"category": "people",
@@ -19463,11 +21762,12 @@
"yes",
"okay",
"accept"
- ]
+ ],
+ "moji": "🙆🏽"
},
"ok_woman_tone4": {
"unicode": "1F646-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "face with ok gesture tone4",
"shortname": ":ok_woman_tone4:",
"category": "people",
@@ -19482,11 +21782,12 @@
"yes",
"okay",
"accept"
- ]
+ ],
+ "moji": "🙆🏾"
},
"ok_woman_tone5": {
"unicode": "1F646-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "face with ok gesture tone5",
"shortname": ":ok_woman_tone5:",
"category": "people",
@@ -19501,26 +21802,30 @@
"yes",
"okay",
"accept"
- ]
+ ],
+ "moji": "🙆🏿"
},
"older_man": {
"unicode": "1F474",
"unicode_alternates": [],
"name": "older man",
"shortname": ":older_man:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"human",
"male",
- "men"
+ "men",
+ "people",
+ "old people",
+ "diversity"
],
"moji": "👴"
},
"older_man_tone1": {
"unicode": "1F474-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "older man tone 1",
"shortname": ":older_man_tone1:",
"category": "people",
@@ -19531,11 +21836,12 @@
"men",
"grandpa",
"grandfather"
- ]
+ ],
+ "moji": "👴🏻"
},
"older_man_tone2": {
"unicode": "1F474-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "older man tone 2",
"shortname": ":older_man_tone2:",
"category": "people",
@@ -19546,11 +21852,12 @@
"men",
"grandpa",
"grandfather"
- ]
+ ],
+ "moji": "👴🏼"
},
"older_man_tone3": {
"unicode": "1F474-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "older man tone 3",
"shortname": ":older_man_tone3:",
"category": "people",
@@ -19561,11 +21868,12 @@
"men",
"grandpa",
"grandfather"
- ]
+ ],
+ "moji": "👴🏽"
},
"older_man_tone4": {
"unicode": "1F474-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "older man tone 4",
"shortname": ":older_man_tone4:",
"category": "people",
@@ -19576,11 +21884,12 @@
"men",
"grandpa",
"grandfather"
- ]
+ ],
+ "moji": "👴🏾"
},
"older_man_tone5": {
"unicode": "1F474-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "older man tone 5",
"shortname": ":older_man_tone5:",
"category": "people",
@@ -19591,14 +21900,15 @@
"men",
"grandpa",
"grandfather"
- ]
+ ],
+ "moji": "👴🏿"
},
"older_woman": {
"unicode": "1F475",
"unicode_alternates": [],
"name": "older woman",
"shortname": ":older_woman:",
- "category": "emoticons",
+ "category": "people",
"aliases": [
":grandma:"
],
@@ -19608,13 +21918,16 @@
"girl",
"women",
"grandma",
- "grandmother"
+ "grandmother",
+ "people",
+ "old people",
+ "diversity"
],
"moji": "👵"
},
"older_woman_tone1": {
"unicode": "1F475-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "older woman tone 1",
"shortname": ":older_woman_tone1:",
"category": "people",
@@ -19628,11 +21941,12 @@
"lady",
"grandma",
"grandmother"
- ]
+ ],
+ "moji": "👵🏻"
},
"older_woman_tone2": {
"unicode": "1F475-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "older woman tone 2",
"shortname": ":older_woman_tone2:",
"category": "people",
@@ -19646,11 +21960,12 @@
"lady",
"grandma",
"grandmother"
- ]
+ ],
+ "moji": "👵🏼"
},
"older_woman_tone3": {
"unicode": "1F475-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "older woman tone 3",
"shortname": ":older_woman_tone3:",
"category": "people",
@@ -19664,11 +21979,12 @@
"lady",
"grandma",
"grandmother"
- ]
+ ],
+ "moji": "👵🏽"
},
"older_woman_tone4": {
"unicode": "1F475-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "older woman tone 4",
"shortname": ":older_woman_tone4:",
"category": "people",
@@ -19682,11 +21998,12 @@
"lady",
"grandma",
"grandmother"
- ]
+ ],
+ "moji": "👵🏾"
},
"older_woman_tone5": {
"unicode": "1F475-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "older woman tone 5",
"shortname": ":older_woman_tone5:",
"category": "people",
@@ -19700,14 +22017,15 @@
"lady",
"grandma",
"grandmother"
- ]
+ ],
+ "moji": "👵🏿"
},
"om_symbol": {
"unicode": "1F549",
"unicode_alternates": [],
"name": "om symbol",
"shortname": ":om_symbol:",
- "category": "objects_symbols",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -19718,20 +22036,24 @@
"dharmic",
"buddhism",
"jainism",
- "meditate"
- ]
+ "meditate",
+ "religion",
+ "symbol"
+ ],
+ "moji": "🕉"
},
"on": {
"unicode": "1F51B",
"unicode_alternates": [],
"name": "on with exclamation mark with left right arrow abo",
"shortname": ":on:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"arrow",
- "words"
+ "words",
+ "symbol"
],
"moji": "🔛"
},
@@ -19740,7 +22062,7 @@
"unicode_alternates": [],
"name": "oncoming automobile",
"shortname": ":oncoming_automobile:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -19748,8 +22070,8 @@
"transportation",
"vehicle",
"sedan",
- "car",
- "automobile"
+ "automobile",
+ "travel"
],
"moji": "🚘"
},
@@ -19758,7 +22080,7 @@
"unicode_alternates": [],
"name": "oncoming bus",
"shortname": ":oncoming_bus:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -19767,8 +22089,8 @@
"bus",
"school",
"city",
- "transportation",
- "public"
+ "public",
+ "travel"
],
"moji": "🚍"
},
@@ -19777,7 +22099,7 @@
"unicode_alternates": [],
"name": "oncoming police car",
"shortname": ":oncoming_police_car:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -19791,7 +22113,9 @@
"citation",
"crime",
"help",
- "officer"
+ "officer",
+ "transportation",
+ "911"
],
"moji": "🚔"
},
@@ -19800,7 +22124,7 @@
"unicode_alternates": [],
"name": "oncoming taxi",
"shortname": ":oncoming_taxi:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -19812,7 +22136,9 @@
"automobile",
"city",
"transport",
- "service"
+ "service",
+ "transportation",
+ "travel"
],
"moji": "🚖"
},
@@ -19822,15 +22148,18 @@
"unicode_alternates": [
"0031-FE0F-20E3"
],
- "name": "digit one",
+ "name": "keycap digit one",
"shortname": ":one:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"1",
"blue-square",
- "numbers"
+ "numbers",
+ "number",
+ "math",
+ "symbol"
]
},
"open_file_folder": {
@@ -19843,7 +22172,9 @@
"aliases_ascii": [],
"keywords": [
"documents",
- "load"
+ "load",
+ "work",
+ "office"
],
"moji": "📂"
},
@@ -19852,18 +22183,22 @@
"unicode_alternates": [],
"name": "open hands sign",
"shortname": ":open_hands:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"butterfly",
- "fingers"
+ "fingers",
+ "body",
+ "hands",
+ "diversity",
+ "condolence"
],
"moji": "👐"
},
"open_hands_tone1": {
"unicode": "1F450-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "open hands sign tone 1",
"shortname": ":open_hands_tone1:",
"category": "people",
@@ -19872,11 +22207,12 @@
"keywords": [
"butterfly",
"fingers"
- ]
+ ],
+ "moji": "👐🏻"
},
"open_hands_tone2": {
"unicode": "1F450-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "open hands sign tone 2",
"shortname": ":open_hands_tone2:",
"category": "people",
@@ -19885,11 +22221,12 @@
"keywords": [
"butterfly",
"fingers"
- ]
+ ],
+ "moji": "👐🏼"
},
"open_hands_tone3": {
"unicode": "1F450-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "open hands sign tone 3",
"shortname": ":open_hands_tone3:",
"category": "people",
@@ -19898,11 +22235,12 @@
"keywords": [
"butterfly",
"fingers"
- ]
+ ],
+ "moji": "👐🏽"
},
"open_hands_tone4": {
"unicode": "1F450-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "open hands sign tone 4",
"shortname": ":open_hands_tone4:",
"category": "people",
@@ -19911,11 +22249,12 @@
"keywords": [
"butterfly",
"fingers"
- ]
+ ],
+ "moji": "👐🏾"
},
"open_hands_tone5": {
"unicode": "1F450-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "open hands sign tone 5",
"shortname": ":open_hands_tone5:",
"category": "people",
@@ -19924,14 +22263,15 @@
"keywords": [
"butterfly",
"fingers"
- ]
+ ],
+ "moji": "👐🏿"
},
"open_mouth": {
"unicode": "1F62E",
"unicode_alternates": [],
"name": "face with open mouth",
"shortname": ":open_mouth:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [
":-O",
@@ -19949,7 +22289,10 @@
"jaw",
"gapping",
"surprise",
- "wow"
+ "wow",
+ "smiley",
+ "surprised",
+ "emotion"
],
"moji": "😮"
},
@@ -19958,7 +22301,7 @@
"unicode_alternates": [],
"name": "ophiuchus",
"shortname": ":ophiuchus:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -19972,28 +22315,11 @@
"zodiac",
"purple-square",
"sign",
- "horoscope"
+ "horoscope",
+ "symbol"
],
"moji": "⛎"
},
- "optical_disk": {
- "unicode": "1F5B8",
- "unicode_alternates": [],
- "name": "optical disc icon",
- "shortname": ":optical_disk:",
- "category": "objects_symbols",
- "aliases": [
- ":optical_disc_icon:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "cd",
- "dvd",
- "disc",
- "disk",
- "technology"
- ]
- },
"orange_book": {
"unicode": "1F4D9",
"unicode_alternates": [],
@@ -20005,13 +22331,17 @@
"keywords": [
"knowledge",
"library",
- "read"
+ "read",
+ "object",
+ "office",
+ "write",
+ "book"
],
"moji": "📙"
},
"orthodox_cross": {
"unicode": "2626",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "orthodox cross",
"shortname": ":orthodox_cross:",
"category": "symbols",
@@ -20021,7 +22351,8 @@
"christian",
"religion",
"symbol"
- ]
+ ],
+ "moji": "☦"
},
"outbox_tray": {
"unicode": "1F4E4",
@@ -20033,10 +22364,23 @@
"aliases_ascii": [],
"keywords": [
"email",
- "inbox"
+ "inbox",
+ "work",
+ "office"
],
"moji": "📤"
},
+ "owl": {
+ "unicode": "1F989",
+ "unicode_alternates": [],
+ "name": "owl",
+ "shortname": ":owl:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🦉"
+ },
"ox": {
"unicode": "1F402",
"unicode_alternates": [],
@@ -20062,22 +22406,12 @@
"aliases_ascii": [],
"keywords": [
"gift",
- "mail"
+ "mail",
+ "object",
+ "office"
],
"moji": "📦"
},
- "page": {
- "unicode": "1F5CF",
- "unicode_alternates": [],
- "name": "page",
- "shortname": ":page:",
- "category": "objects_symbols",
- "aliases": [],
- "aliases_ascii": [],
- "keywords": [
- "document"
- ]
- },
"page_facing_up": {
"unicode": "1F4C4",
"unicode_alternates": [],
@@ -20087,7 +22421,10 @@
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "documents"
+ "documents",
+ "work",
+ "office",
+ "write"
],
"moji": "📄"
},
@@ -20100,7 +22437,9 @@
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "documents"
+ "documents",
+ "office",
+ "write"
],
"moji": "📃"
},
@@ -20114,28 +22453,18 @@
"aliases_ascii": [],
"keywords": [
"bbcall",
- "oldschool"
+ "oldschool",
+ "electronics",
+ "work"
],
"moji": "📟"
},
- "pages": {
- "unicode": "1F5D0",
- "unicode_alternates": [],
- "name": "pages",
- "shortname": ":pages:",
- "category": "objects_symbols",
- "aliases": [],
- "aliases_ascii": [],
- "keywords": [
- "documents"
- ]
- },
"paintbrush": {
"unicode": "1F58C",
"unicode_alternates": [],
"name": "lower left paintbrush",
"shortname": ":paintbrush:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [
":lower_left_paintbrush:"
],
@@ -20143,8 +22472,12 @@
"keywords": [
"brush",
"art",
- "painting"
- ]
+ "painting",
+ "object",
+ "office",
+ "write"
+ ],
+ "moji": "🖌"
},
"palm_tree": {
"unicode": "1F334",
@@ -20163,10 +22496,22 @@
"coconuts",
"fronds",
"warm",
- "tropical"
+ "tropical",
+ "trees"
],
"moji": "🌴"
},
+ "pancakes": {
+ "unicode": "1F95E",
+ "unicode_alternates": [],
+ "name": "pancakes",
+ "shortname": ":pancakes:",
+ "category": "food",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🥞"
+ },
"panda_face": {
"unicode": "1F43C",
"unicode_alternates": [],
@@ -20189,7 +22534,9 @@
"bamboo",
"china",
"black",
- "white"
+ "white",
+ "wildlife",
+ "roar"
],
"moji": "🐼"
},
@@ -20203,7 +22550,10 @@
"aliases_ascii": [],
"keywords": [
"documents",
- "stationery"
+ "stationery",
+ "object",
+ "work",
+ "office"
],
"moji": "📎"
},
@@ -20212,22 +22562,26 @@
"unicode_alternates": [],
"name": "linked paperclips",
"shortname": ":paperclips:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [
":linked_paperclips:"
],
"aliases_ascii": [],
"keywords": [
"documents",
- "stationery"
- ]
+ "stationery",
+ "object",
+ "work",
+ "office"
+ ],
+ "moji": "🖇"
},
"park": {
"unicode": "1F3DE",
"unicode_alternates": [],
"name": "national park",
"shortname": ":park:",
- "category": "travel_places",
+ "category": "travel",
"aliases": [
":national_park:"
],
@@ -20238,8 +22592,13 @@
"wildlife",
"forest",
"wilderness",
- "national"
- ]
+ "national",
+ "travel",
+ "vacation",
+ "park",
+ "camp"
+ ],
+ "moji": "🏞"
},
"parking": {
"unicode": "1F17F",
@@ -20248,14 +22607,15 @@
],
"name": "negative squared latin capital letter p",
"shortname": ":parking:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"alphabet",
"blue-square",
"cars",
- "letter"
+ "letter",
+ "symbol"
],
"moji": "🅿"
},
@@ -20266,7 +22626,7 @@
],
"name": "part alternation mark",
"shortname": ":part_alternation_mark:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -20279,7 +22639,8 @@
"cue",
"letter",
"m",
- "japanese"
+ "japanese",
+ "symbol"
],
"moji": "〽"
},
@@ -20297,7 +22658,9 @@
"cloud",
"morning",
"nature",
- "weather"
+ "weather",
+ "sky",
+ "sun"
],
"moji": "⛅"
},
@@ -20306,7 +22669,7 @@
"unicode_alternates": [],
"name": "passport control",
"shortname": ":passport_control:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -20317,13 +22680,14 @@
"travel",
"control",
"foreign",
- "identification"
+ "identification",
+ "symbol"
],
"moji": "🛂"
},
"pause_button": {
"unicode": "23F8",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "double vertical bar",
"shortname": ":pause_button:",
"category": "symbols",
@@ -20335,11 +22699,12 @@
"pause",
"sound",
"symbol"
- ]
+ ],
+ "moji": "⏸"
},
"peace": {
"unicode": "262E",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "peace symbol",
"shortname": ":peace:",
"category": "symbols",
@@ -20348,15 +22713,19 @@
],
"aliases_ascii": [],
"keywords": [
- "sign"
- ]
+ "sign",
+ "symbol",
+ "peace",
+ "drugs"
+ ],
+ "moji": "☮"
},
"peach": {
"unicode": "1F351",
"unicode_alternates": [],
"name": "peach",
"shortname": ":peach:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -20364,26 +22733,39 @@
"fruit",
"nature",
"peach",
- "fruit",
"juicy",
- "pit"
+ "pit",
+ "butt"
],
"moji": "🍑"
},
+ "peanuts": {
+ "unicode": "1F95C",
+ "unicode_alternates": [],
+ "name": "peanuts",
+ "shortname": ":peanuts:",
+ "category": "food",
+ "aliases": [
+ ":shelled_peanut:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🥜"
+ },
"pear": {
"unicode": "1F350",
"unicode_alternates": [],
"name": "pear",
"shortname": ":pear:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"fruit",
"nature",
"pear",
- "fruit",
- "shape"
+ "shape",
+ "food"
],
"moji": "🍐"
},
@@ -20392,7 +22774,7 @@
"unicode_alternates": [],
"name": "lower left ballpoint pen",
"shortname": ":pen_ballpoint:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [
":lower_left_ballpoint_pen:"
],
@@ -20400,15 +22782,18 @@
"keywords": [
"write",
"bic",
- "ink"
- ]
+ "ink",
+ "object",
+ "office"
+ ],
+ "moji": "🖊"
},
"pen_fountain": {
"unicode": "1F58B",
"unicode_alternates": [],
"name": "lower left fountain pen",
"shortname": ":pen_fountain:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [
":lower_left_fountain_pen:"
],
@@ -20416,8 +22801,11 @@
"keywords": [
"write",
"calligraphy",
- "ink"
- ]
+ "ink",
+ "object",
+ "office"
+ ],
+ "moji": "🖋"
},
"pencil": {
"unicode": "1F4DD",
@@ -20433,7 +22821,9 @@
"documents",
"paper",
"station",
- "write"
+ "write",
+ "work",
+ "office"
],
"moji": "📝"
},
@@ -20450,26 +22840,12 @@
"keywords": [
"paper",
"stationery",
- "write"
+ "write",
+ "object",
+ "office"
],
"moji": "✏"
},
- "pencil3": {
- "unicode": "1F589",
- "unicode_alternates": [],
- "name": "lower left pencil",
- "shortname": ":pencil3:",
- "category": "objects_symbols",
- "aliases": [
- ":lower_left_pencil:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "paper",
- "stationery",
- "write"
- ]
- },
"penguin": {
"unicode": "1F427",
"unicode_alternates": [],
@@ -20480,46 +22856,17 @@
"aliases_ascii": [],
"keywords": [
"animal",
- "nature"
+ "nature",
+ "wildlife"
],
"moji": "🐧"
},
- "pennant_black": {
- "unicode": "1F3F2",
- "unicode_alternates": [],
- "name": "black pennant",
- "shortname": ":pennant_black:",
- "category": "objects_symbols",
- "aliases": [
- ":black_pennant:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "flag",
- "athletics"
- ]
- },
- "pennant_white": {
- "unicode": "1F3F1",
- "unicode_alternates": [],
- "name": "white pennant",
- "shortname": ":pennant_white:",
- "category": "objects_symbols",
- "aliases": [
- ":white_pennant:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "flag",
- "athletics"
- ]
- },
"pensive": {
"unicode": "1F614",
"unicode_alternates": [],
"name": "pensive face",
"shortname": ":pensive:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -20532,7 +22879,10 @@
"reflective",
"wistful",
"meditate",
- "serious"
+ "serious",
+ "smiley",
+ "emotion",
+ "rip"
],
"moji": "😔"
},
@@ -20541,7 +22891,7 @@
"unicode_alternates": [],
"name": "performing arts",
"shortname": ":performing_arts:",
- "category": "places",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -20552,10 +22902,11 @@
"arts",
"performance",
"entertainment",
- "acting",
"story",
"mask",
- "masks"
+ "masks",
+ "theatre",
+ "movie"
],
"moji": "🎭"
},
@@ -20564,7 +22915,7 @@
"unicode_alternates": [],
"name": "persevering face",
"shortname": ":persevere:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [
">.<"
@@ -20575,7 +22926,11 @@
"face",
"no",
"sick",
- "upset"
+ "upset",
+ "sad",
+ "smiley",
+ "angry",
+ "emotion"
],
"moji": "😣"
},
@@ -20584,7 +22939,7 @@
"unicode_alternates": [],
"name": "person frowning",
"shortname": ":person_frowning:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -20594,13 +22949,16 @@
"dejected",
"rejected",
"sad",
- "frown"
+ "frown",
+ "people",
+ "women",
+ "diversity"
],
"moji": "🙍"
},
"person_frowning_tone1": {
"unicode": "1F64D-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person frowning tone 1",
"shortname": ":person_frowning_tone1:",
"category": "people",
@@ -20614,11 +22972,12 @@
"rejected",
"sad",
"frown"
- ]
+ ],
+ "moji": "🙍🏻"
},
"person_frowning_tone2": {
"unicode": "1F64D-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person frowning tone 2",
"shortname": ":person_frowning_tone2:",
"category": "people",
@@ -20632,11 +22991,12 @@
"rejected",
"sad",
"frown"
- ]
+ ],
+ "moji": "🙍🏼"
},
"person_frowning_tone3": {
"unicode": "1F64D-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person frowning tone 3",
"shortname": ":person_frowning_tone3:",
"category": "people",
@@ -20650,11 +23010,12 @@
"rejected",
"sad",
"frown"
- ]
+ ],
+ "moji": "🙍🏽"
},
"person_frowning_tone4": {
"unicode": "1F64D-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person frowning tone 4",
"shortname": ":person_frowning_tone4:",
"category": "people",
@@ -20668,11 +23029,12 @@
"rejected",
"sad",
"frown"
- ]
+ ],
+ "moji": "🙍🏾"
},
"person_frowning_tone5": {
"unicode": "1F64D-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person frowning tone 5",
"shortname": ":person_frowning_tone5:",
"category": "people",
@@ -20686,14 +23048,15 @@
"rejected",
"sad",
"frown"
- ]
+ ],
+ "moji": "🙍🏿"
},
"person_with_blond_hair": {
"unicode": "1F471",
"unicode_alternates": [],
"name": "person with blond hair",
"shortname": ":person_with_blond_hair:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -20703,13 +23066,16 @@
"young",
"western",
"westerner",
- "occidental"
+ "occidental",
+ "people",
+ "men",
+ "diversity"
],
"moji": "👱"
},
"person_with_blond_hair_tone1": {
"unicode": "1F471-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person with blond hair tone 1",
"shortname": ":person_with_blond_hair_tone1:",
"category": "people",
@@ -20723,11 +23089,12 @@
"western",
"westerner",
"occidental"
- ]
+ ],
+ "moji": "👱🏻"
},
"person_with_blond_hair_tone2": {
"unicode": "1F471-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person with blond hair tone 2",
"shortname": ":person_with_blond_hair_tone2:",
"category": "people",
@@ -20741,11 +23108,12 @@
"western",
"westerner",
"occidental"
- ]
+ ],
+ "moji": "👱🏼"
},
"person_with_blond_hair_tone3": {
"unicode": "1F471-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person with blond hair tone 3",
"shortname": ":person_with_blond_hair_tone3:",
"category": "people",
@@ -20759,11 +23127,12 @@
"western",
"westerner",
"occidental"
- ]
+ ],
+ "moji": "👱🏽"
},
"person_with_blond_hair_tone4": {
"unicode": "1F471-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person with blond hair tone 4",
"shortname": ":person_with_blond_hair_tone4:",
"category": "people",
@@ -20777,11 +23146,12 @@
"western",
"westerner",
"occidental"
- ]
+ ],
+ "moji": "👱🏾"
},
"person_with_blond_hair_tone5": {
"unicode": "1F471-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person with blond hair tone 5",
"shortname": ":person_with_blond_hair_tone5:",
"category": "people",
@@ -20795,14 +23165,15 @@
"western",
"westerner",
"occidental"
- ]
+ ],
+ "moji": "👱🏿"
},
"person_with_pouting_face": {
"unicode": "1F64E",
"unicode_alternates": [],
"name": "person with pouting face",
"shortname": ":person_with_pouting_face:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -20812,13 +23183,16 @@
"pout",
"sexy",
"cute",
- "annoyed"
+ "annoyed",
+ "people",
+ "women",
+ "diversity"
],
"moji": "🙎"
},
"person_with_pouting_face_tone1": {
"unicode": "1F64E-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person with pouting face tone1",
"shortname": ":person_with_pouting_face_tone1:",
"category": "people",
@@ -20832,11 +23206,12 @@
"sexy",
"cute",
"annoyed"
- ]
+ ],
+ "moji": "🙎🏻"
},
"person_with_pouting_face_tone2": {
"unicode": "1F64E-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person with pouting face tone2",
"shortname": ":person_with_pouting_face_tone2:",
"category": "people",
@@ -20850,11 +23225,12 @@
"sexy",
"cute",
"annoyed"
- ]
+ ],
+ "moji": "🙎🏼"
},
"person_with_pouting_face_tone3": {
"unicode": "1F64E-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person with pouting face tone3",
"shortname": ":person_with_pouting_face_tone3:",
"category": "people",
@@ -20868,11 +23244,12 @@
"sexy",
"cute",
"annoyed"
- ]
+ ],
+ "moji": "🙎🏽"
},
"person_with_pouting_face_tone4": {
"unicode": "1F64E-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person with pouting face tone4",
"shortname": ":person_with_pouting_face_tone4:",
"category": "people",
@@ -20886,11 +23263,12 @@
"sexy",
"cute",
"annoyed"
- ]
+ ],
+ "moji": "🙎🏾"
},
"person_with_pouting_face_tone5": {
"unicode": "1F64E-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person with pouting face tone5",
"shortname": ":person_with_pouting_face_tone5:",
"category": "people",
@@ -20904,11 +23282,12 @@
"sexy",
"cute",
"annoyed"
- ]
+ ],
+ "moji": "🙎🏿"
},
"pick": {
"unicode": "26CF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "pick",
"shortname": ":pick:",
"category": "objects",
@@ -20917,8 +23296,10 @@
"keywords": [
"mining",
"object",
- "tool"
- ]
+ "tool",
+ "weapon"
+ ],
+ "moji": "⛏"
},
"pig": {
"unicode": "1F437",
@@ -20976,7 +23357,6 @@
"food",
"eat",
"cute",
- "oink",
"pink",
"smell",
"truffle"
@@ -20993,7 +23373,9 @@
"aliases_ascii": [],
"keywords": [
"health",
- "medicine"
+ "medicine",
+ "object",
+ "drugs"
],
"moji": "💊"
},
@@ -21002,7 +23384,7 @@
"unicode_alternates": [],
"name": "pineapple",
"shortname": ":pineapple:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -21018,7 +23400,7 @@
},
"ping_pong": {
"unicode": "1F3D3",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "table tennis paddle and ball",
"shortname": ":ping_pong:",
"category": "activity",
@@ -21026,22 +23408,13 @@
":table_tennis:"
],
"aliases_ascii": [],
- "keywords": []
- },
- "piracy": {
- "unicode": "1F572",
- "unicode_alternates": [],
- "name": "no piracy",
- "shortname": ":piracy:",
- "category": "objects_symbols",
- "aliases": [
- ":no_piracy:"
- ],
- "aliases_ascii": [],
"keywords": [
- "theft",
- "rule"
- ]
+ "game",
+ "ball",
+ "sport",
+ "ping pong"
+ ],
+ "moji": "🏓"
},
"pisces": {
"unicode": "2653",
@@ -21050,7 +23423,7 @@
],
"name": "pisces",
"shortname": ":pisces:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -21063,9 +23436,8 @@
"zodiac",
"sign",
"purple-square",
- "sign",
- "zodiac",
- "horoscope"
+ "horoscope",
+ "symbol"
],
"moji": "♓"
},
@@ -21074,7 +23446,7 @@
"unicode_alternates": [],
"name": "slice of pizza",
"shortname": ":pizza:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -21086,13 +23458,14 @@
"italian",
"italy",
"slice",
- "peperoni"
+ "peperoni",
+ "boys night"
],
"moji": "🍕"
},
"place_of_worship": {
"unicode": "1F6D0",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "place of worship",
"shortname": ":place_of_worship:",
"category": "symbols",
@@ -21100,11 +23473,16 @@
":worship_symbol:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "religion",
+ "symbol",
+ "pray"
+ ],
+ "moji": "🛐"
},
"play_pause": {
"unicode": "23EF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "black right-pointing double triangle with double vertical bar",
"shortname": ":play_pause:",
"category": "symbols",
@@ -21117,26 +23495,30 @@
"right",
"sound",
"symbol"
- ]
+ ],
+ "moji": "⏯"
},
"point_down": {
"unicode": "1F447",
"unicode_alternates": [],
"name": "white down pointing backhand index",
"shortname": ":point_down:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"direction",
"fingers",
- "hand"
+ "hand",
+ "body",
+ "hands",
+ "diversity"
],
"moji": "👇"
},
"point_down_tone1": {
"unicode": "1F447-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "white down pointing backhand index tone 1",
"shortname": ":point_down_tone1:",
"category": "people",
@@ -21146,11 +23528,12 @@
"direction",
"finger",
"hand"
- ]
+ ],
+ "moji": "👇🏻"
},
"point_down_tone2": {
"unicode": "1F447-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "white down pointing backhand index tone 2",
"shortname": ":point_down_tone2:",
"category": "people",
@@ -21160,11 +23543,12 @@
"direction",
"finger",
"hand"
- ]
+ ],
+ "moji": "👇🏼"
},
"point_down_tone3": {
"unicode": "1F447-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "white down pointing backhand index tone 3",
"shortname": ":point_down_tone3:",
"category": "people",
@@ -21174,11 +23558,12 @@
"direction",
"finger",
"hand"
- ]
+ ],
+ "moji": "👇🏽"
},
"point_down_tone4": {
"unicode": "1F447-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "white down pointing backhand index tone 4",
"shortname": ":point_down_tone4:",
"category": "people",
@@ -21188,11 +23573,12 @@
"direction",
"finger",
"hand"
- ]
+ ],
+ "moji": "👇🏾"
},
"point_down_tone5": {
"unicode": "1F447-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "white down pointing backhand index tone 5",
"shortname": ":point_down_tone5:",
"category": "people",
@@ -21202,26 +23588,31 @@
"direction",
"finger",
"hand"
- ]
+ ],
+ "moji": "👇🏿"
},
"point_left": {
"unicode": "1F448",
"unicode_alternates": [],
"name": "white left pointing backhand index",
"shortname": ":point_left:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"direction",
"fingers",
- "hand"
+ "hand",
+ "body",
+ "hands",
+ "hi",
+ "diversity"
],
"moji": "👈"
},
"point_left_tone1": {
"unicode": "1F448-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "white left pointing backhand index tone 1",
"shortname": ":point_left_tone1:",
"category": "people",
@@ -21231,11 +23622,12 @@
"direction",
"finger",
"hand"
- ]
+ ],
+ "moji": "👈🏻"
},
"point_left_tone2": {
"unicode": "1F448-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "white left pointing backhand index tone 2",
"shortname": ":point_left_tone2:",
"category": "people",
@@ -21245,11 +23637,12 @@
"direction",
"finger",
"hand"
- ]
+ ],
+ "moji": "👈🏼"
},
"point_left_tone3": {
"unicode": "1F448-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "white left pointing backhand index tone 3",
"shortname": ":point_left_tone3:",
"category": "people",
@@ -21259,11 +23652,12 @@
"direction",
"finger",
"hand"
- ]
+ ],
+ "moji": "👈🏽"
},
"point_left_tone4": {
"unicode": "1F448-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "white left pointing backhand index tone 4",
"shortname": ":point_left_tone4:",
"category": "people",
@@ -21273,11 +23667,12 @@
"direction",
"finger",
"hand"
- ]
+ ],
+ "moji": "👈🏾"
},
"point_left_tone5": {
"unicode": "1F448-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "white left pointing backhand index tone 5",
"shortname": ":point_left_tone5:",
"category": "people",
@@ -21287,26 +23682,31 @@
"direction",
"finger",
"hand"
- ]
+ ],
+ "moji": "👈🏿"
},
"point_right": {
"unicode": "1F449",
"unicode_alternates": [],
"name": "white right pointing backhand index",
"shortname": ":point_right:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"direction",
"fingers",
- "hand"
+ "hand",
+ "body",
+ "hands",
+ "hi",
+ "diversity"
],
"moji": "👉"
},
"point_right_tone1": {
"unicode": "1F449-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "white right pointing backhand index tone 1",
"shortname": ":point_right_tone1:",
"category": "people",
@@ -21316,11 +23716,12 @@
"direction",
"finger",
"hand"
- ]
+ ],
+ "moji": "👉🏻"
},
"point_right_tone2": {
"unicode": "1F449-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "white right pointing backhand index tone 2",
"shortname": ":point_right_tone2:",
"category": "people",
@@ -21330,11 +23731,12 @@
"direction",
"finger",
"hand"
- ]
+ ],
+ "moji": "👉🏼"
},
"point_right_tone3": {
"unicode": "1F449-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "white right pointing backhand index tone 3",
"shortname": ":point_right_tone3:",
"category": "people",
@@ -21344,11 +23746,12 @@
"direction",
"finger",
"hand"
- ]
+ ],
+ "moji": "👉🏽"
},
"point_right_tone4": {
"unicode": "1F449-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "white right pointing backhand index tone 4",
"shortname": ":point_right_tone4:",
"category": "people",
@@ -21358,11 +23761,12 @@
"direction",
"finger",
"hand"
- ]
+ ],
+ "moji": "👉🏾"
},
"point_right_tone5": {
"unicode": "1F449-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "white right pointing backhand index tone 5",
"shortname": ":point_right_tone5:",
"category": "people",
@@ -21372,7 +23776,8 @@
"direction",
"finger",
"hand"
- ]
+ ],
+ "moji": "👉🏿"
},
"point_up": {
"unicode": "261D",
@@ -21381,13 +23786,17 @@
],
"name": "white up pointing index",
"shortname": ":point_up:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"direction",
"fingers",
- "hand"
+ "hand",
+ "body",
+ "hands",
+ "emojione",
+ "diversity"
],
"moji": "☝"
},
@@ -21396,19 +23805,22 @@
"unicode_alternates": [],
"name": "white up pointing backhand index",
"shortname": ":point_up_2:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"direction",
"fingers",
- "hand"
+ "hand",
+ "body",
+ "hands",
+ "diversity"
],
"moji": "👆"
},
"point_up_2_tone1": {
"unicode": "1F446-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "white up pointing backhand index tone 1",
"shortname": ":point_up_2_tone1:",
"category": "people",
@@ -21419,11 +23831,12 @@
"finger",
"hand",
"one"
- ]
+ ],
+ "moji": "👆🏻"
},
"point_up_2_tone2": {
"unicode": "1F446-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "white up pointing backhand index tone 2",
"shortname": ":point_up_2_tone2:",
"category": "people",
@@ -21434,11 +23847,12 @@
"finger",
"hand",
"one"
- ]
+ ],
+ "moji": "👆🏼"
},
"point_up_2_tone3": {
"unicode": "1F446-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "white up pointing backhand index tone 3",
"shortname": ":point_up_2_tone3:",
"category": "people",
@@ -21449,11 +23863,12 @@
"finger",
"hand",
"one"
- ]
+ ],
+ "moji": "👆🏽"
},
"point_up_2_tone4": {
"unicode": "1F446-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "white up pointing backhand index tone 4",
"shortname": ":point_up_2_tone4:",
"category": "people",
@@ -21464,11 +23879,12 @@
"finger",
"hand",
"one"
- ]
+ ],
+ "moji": "👆🏾"
},
"point_up_2_tone5": {
"unicode": "1F446-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "white up pointing backhand index tone 5",
"shortname": ":point_up_2_tone5:",
"category": "people",
@@ -21479,11 +23895,12 @@
"finger",
"hand",
"one"
- ]
+ ],
+ "moji": "👆🏿"
},
"point_up_tone1": {
"unicode": "261D-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "white up pointing index tone 1",
"shortname": ":point_up_tone1:",
"category": "people",
@@ -21494,11 +23911,12 @@
"finger",
"hand",
"one"
- ]
+ ],
+ "moji": "☝🏻"
},
"point_up_tone2": {
"unicode": "261D-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "white up pointing index tone 2",
"shortname": ":point_up_tone2:",
"category": "people",
@@ -21509,11 +23927,12 @@
"finger",
"hand",
"one"
- ]
+ ],
+ "moji": "☝🏼"
},
"point_up_tone3": {
"unicode": "261D-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "white up pointing index tone 3",
"shortname": ":point_up_tone3:",
"category": "people",
@@ -21524,11 +23943,12 @@
"finger",
"hand",
"one"
- ]
+ ],
+ "moji": "☝🏽"
},
"point_up_tone4": {
"unicode": "261D-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "white up pointing index tone 4",
"shortname": ":point_up_tone4:",
"category": "people",
@@ -21539,11 +23959,12 @@
"finger",
"hand",
"one"
- ]
+ ],
+ "moji": "☝🏾"
},
"point_up_tone5": {
"unicode": "261D-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "white up pointing index tone 5",
"shortname": ":point_up_tone5:",
"category": "people",
@@ -21554,14 +23975,15 @@
"finger",
"hand",
"one"
- ]
+ ],
+ "moji": "☝🏿"
},
"police_car": {
"unicode": "1F693",
"unicode_alternates": [],
"name": "police car",
"shortname": ":police_car:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -21577,7 +23999,8 @@
"citation",
"crime",
"help",
- "officer"
+ "officer",
+ "911"
],
"moji": "🚓"
},
@@ -21595,7 +24018,6 @@
"dog",
"nature",
"poodle",
- "dog",
"clip",
"showy",
"sophisticated",
@@ -21608,7 +24030,7 @@
"unicode_alternates": [],
"name": "pile of poo",
"shortname": ":poop:",
- "category": "emoticons",
+ "category": "people",
"aliases": [
":shit:",
":hankey:",
@@ -21620,32 +24042,41 @@
"shit",
"shitface",
"turd",
- "poo"
+ "poo",
+ "bathroom",
+ "sol",
+ "diarrhea"
],
"moji": "💩"
},
"popcorn": {
"unicode": "1F37F",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "popcorn",
"shortname": ":popcorn:",
- "category": "foods",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "food",
+ "parties"
+ ],
+ "moji": "🍿"
},
"post_office": {
"unicode": "1F3E3",
"unicode_alternates": [],
"name": "japanese post office",
"shortname": ":post_office:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"building",
"communication",
- "email"
+ "email",
+ "places",
+ "post office"
],
"moji": "🏣"
},
@@ -21659,7 +24090,8 @@
"aliases_ascii": [],
"keywords": [
"instrument",
- "music"
+ "music",
+ "object"
],
"moji": "📯"
},
@@ -21674,7 +24106,8 @@
"keywords": [
"email",
"envelope",
- "letter"
+ "letter",
+ "object"
],
"moji": "📮"
},
@@ -21683,7 +24116,7 @@
"unicode_alternates": [],
"name": "potable water symbol",
"shortname": ":potable_water:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -21699,27 +24132,40 @@
"clear",
"clean",
"aqua",
- "h20"
+ "h20",
+ "symbol"
],
"moji": "🚰"
},
+ "potato": {
+ "unicode": "1F954",
+ "unicode_alternates": [],
+ "name": "potato",
+ "shortname": ":potato:",
+ "category": "food",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🥔"
+ },
"pouch": {
"unicode": "1F45D",
"unicode_alternates": [],
"name": "pouch",
"shortname": ":pouch:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"accessories",
"bag",
"pouch",
- "bag",
"cosmetic",
"packing",
"grandma",
- "makeup"
+ "makeup",
+ "women",
+ "fashion"
],
"moji": "👝"
},
@@ -21728,7 +24174,7 @@
"unicode_alternates": [],
"name": "poultry leg",
"shortname": ":poultry_leg:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -21737,7 +24183,8 @@
"poultry",
"leg",
"chicken",
- "fried"
+ "fried",
+ "holidays"
],
"moji": "🍗"
},
@@ -21759,13 +24206,9 @@
"uk",
"pound",
"britain",
- "british",
"banknote",
- "money",
- "currency",
"paper",
- "cash",
- "bills"
+ "cash"
],
"moji": "💷"
},
@@ -21774,7 +24217,7 @@
"unicode_alternates": [],
"name": "pouting cat face",
"shortname": ":pouting_cat:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -21784,7 +24227,8 @@
"annoyed",
"miffed",
"glower",
- "frown"
+ "frown",
+ "cat"
],
"moji": "😾"
},
@@ -21793,7 +24237,7 @@
"unicode_alternates": [],
"name": "person with folded hands",
"shortname": ":pray:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -21807,13 +24251,19 @@
"hands",
"sorrow",
"regret",
- "sorry"
+ "sorry",
+ "body",
+ "hi",
+ "luck",
+ "thank you",
+ "diversity",
+ "scientology"
],
"moji": "🙏"
},
"pray_tone1": {
"unicode": "1F64F-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person with folded hands tone 1",
"shortname": ":pray_tone1:",
"category": "people",
@@ -21830,11 +24280,12 @@
"sorrow",
"regret",
"sorry"
- ]
+ ],
+ "moji": "🙏🏻"
},
"pray_tone2": {
"unicode": "1F64F-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person with folded hands tone 2",
"shortname": ":pray_tone2:",
"category": "people",
@@ -21851,11 +24302,12 @@
"sorrow",
"regret",
"sorry"
- ]
+ ],
+ "moji": "🙏🏼"
},
"pray_tone3": {
"unicode": "1F64F-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person with folded hands tone 3",
"shortname": ":pray_tone3:",
"category": "people",
@@ -21872,11 +24324,12 @@
"sorrow",
"regret",
"sorry"
- ]
+ ],
+ "moji": "🙏🏽"
},
"pray_tone4": {
"unicode": "1F64F-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person with folded hands tone 4",
"shortname": ":pray_tone4:",
"category": "people",
@@ -21893,11 +24346,12 @@
"sorrow",
"regret",
"sorry"
- ]
+ ],
+ "moji": "🙏🏾"
},
"pray_tone5": {
"unicode": "1F64F-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person with folded hands tone 5",
"shortname": ":pray_tone5:",
"category": "people",
@@ -21914,24 +24368,173 @@
"sorrow",
"regret",
"sorry"
- ]
+ ],
+ "moji": "🙏🏿"
},
"prayer_beads": {
"unicode": "1F4FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "prayer beads",
"shortname": ":prayer_beads:",
"category": "objects",
"aliases": [],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "object",
+ "rosary"
+ ],
+ "moji": "📿"
+ },
+ "pregnant_woman": {
+ "unicode": "1F930",
+ "unicode_alternates": [],
+ "name": "pregnant woman",
+ "shortname": ":pregnant_woman:",
+ "category": "people",
+ "aliases": [
+ ":expecting_woman:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤰"
+ },
+ "pregnant_woman_tone1": {
+ "unicode": "1F930-1F3FB",
+ "unicode_alternates": [],
+ "name": "pregnant woman tone 1",
+ "shortname": ":pregnant_woman_tone1:",
+ "category": "people",
+ "aliases": [
+ ":expecting_woman_tone1:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤰🏻"
+ },
+ "pregnant_woman_tone2": {
+ "unicode": "1F930-1F3FC",
+ "unicode_alternates": [],
+ "name": "pregnant woman tone 2",
+ "shortname": ":pregnant_woman_tone2:",
+ "category": "people",
+ "aliases": [
+ ":expecting_woman_tone2:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤰🏼"
+ },
+ "pregnant_woman_tone3": {
+ "unicode": "1F930-1F3FD",
+ "unicode_alternates": [],
+ "name": "pregnant woman tone 3",
+ "shortname": ":pregnant_woman_tone3:",
+ "category": "people",
+ "aliases": [
+ ":expecting_woman_tone3:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤰🏽"
+ },
+ "pregnant_woman_tone4": {
+ "unicode": "1F930-1F3FE",
+ "unicode_alternates": [],
+ "name": "pregnant woman tone 4",
+ "shortname": ":pregnant_woman_tone4:",
+ "category": "people",
+ "aliases": [
+ ":expecting_woman_tone4:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤰🏾"
+ },
+ "pregnant_woman_tone5": {
+ "unicode": "1F930-1F3FF",
+ "unicode_alternates": [],
+ "name": "pregnant woman tone 5",
+ "shortname": ":pregnant_woman_tone5:",
+ "category": "people",
+ "aliases": [
+ ":expecting_woman_tone5:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤰🏿"
+ },
+ "prince": {
+ "unicode": "1F934",
+ "unicode_alternates": [],
+ "name": "prince",
+ "shortname": ":prince:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤴"
+ },
+ "prince_tone1": {
+ "unicode": "1F934-1F3FB",
+ "unicode_alternates": [],
+ "name": "prince tone 1",
+ "shortname": ":prince_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤴🏻"
+ },
+ "prince_tone2": {
+ "unicode": "1F934-1F3FC",
+ "unicode_alternates": [],
+ "name": "prince tone 2",
+ "shortname": ":prince_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤴🏼"
+ },
+ "prince_tone3": {
+ "unicode": "1F934-1F3FD",
+ "unicode_alternates": [],
+ "name": "prince tone 3",
+ "shortname": ":prince_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤴🏽"
+ },
+ "prince_tone4": {
+ "unicode": "1F934-1F3FE",
+ "unicode_alternates": [],
+ "name": "prince tone 4",
+ "shortname": ":prince_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤴🏾"
+ },
+ "prince_tone5": {
+ "unicode": "1F934-1F3FF",
+ "unicode_alternates": [],
+ "name": "prince tone 5",
+ "shortname": ":prince_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤴🏿"
},
"princess": {
"unicode": "1F478",
"unicode_alternates": [],
"name": "princess",
"shortname": ":princess:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -21947,13 +24550,18 @@
"queen",
"daughter",
"disney",
- "high-maintenance"
+ "high-maintenance",
+ "people",
+ "women",
+ "diversity",
+ "beautiful",
+ "girls night"
],
"moji": "👸"
},
"princess_tone1": {
"unicode": "1F478-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "princess tone 1",
"shortname": ":princess_tone1:",
"category": "people",
@@ -21972,11 +24580,12 @@
"daughter",
"disney",
"high-maintenance"
- ]
+ ],
+ "moji": "👸🏻"
},
"princess_tone2": {
"unicode": "1F478-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "princess tone 2",
"shortname": ":princess_tone2:",
"category": "people",
@@ -21995,11 +24604,12 @@
"daughter",
"disney",
"high-maintenance"
- ]
+ ],
+ "moji": "👸🏼"
},
"princess_tone3": {
"unicode": "1F478-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "princess tone 3",
"shortname": ":princess_tone3:",
"category": "people",
@@ -22018,11 +24628,12 @@
"daughter",
"disney",
"high-maintenance"
- ]
+ ],
+ "moji": "👸🏽"
},
"princess_tone4": {
"unicode": "1F478-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "princess tone 4",
"shortname": ":princess_tone4:",
"category": "people",
@@ -22041,11 +24652,12 @@
"daughter",
"disney",
"high-maintenance"
- ]
+ ],
+ "moji": "👸🏾"
},
"princess_tone5": {
"unicode": "1F478-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "princess tone 5",
"shortname": ":princess_tone5:",
"category": "people",
@@ -22064,49 +24676,34 @@
"daughter",
"disney",
"high-maintenance"
- ]
+ ],
+ "moji": "👸🏿"
},
"printer": {
"unicode": "1F5A8",
"unicode_alternates": [],
"name": "printer",
"shortname": ":printer:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"hardcopy",
"paper",
"inkjet",
- "laser"
- ]
- },
- "prohibited": {
- "unicode": "1F6C7",
- "unicode_alternates": [],
- "name": "prohibited sign",
- "shortname": ":prohibited:",
- "category": "objects_symbols",
- "aliases": [
- ":prohibited_sign:"
+ "laser",
+ "electronics",
+ "work",
+ "office"
],
- "aliases_ascii": [],
- "keywords": [
- "no",
- "not",
- "denied",
- "disallow",
- "forbid",
- "limit",
- "stop"
- ]
+ "moji": "🖨"
},
"projector": {
"unicode": "1F4FD",
"unicode_alternates": [],
"name": "film projector",
"shortname": ":projector:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [
":film_projector:"
],
@@ -22117,26 +24714,35 @@
"motion",
"picture",
"8mm",
- "16mm"
- ]
+ "16mm",
+ "object",
+ "camera"
+ ],
+ "moji": "📽"
},
"punch": {
"unicode": "1F44A",
"unicode_alternates": [],
"name": "fisted hand sign",
"shortname": ":punch:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"fist",
- "hand"
+ "hand",
+ "body",
+ "hands",
+ "hi",
+ "fist bump",
+ "diversity",
+ "boys night"
],
"moji": "👊"
},
"punch_tone1": {
"unicode": "1F44A-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "fisted hand sign tone 1",
"shortname": ":punch_tone1:",
"category": "people",
@@ -22145,11 +24751,12 @@
"keywords": [
"fist",
"punch"
- ]
+ ],
+ "moji": "👊🏻"
},
"punch_tone2": {
"unicode": "1F44A-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "fisted hand sign tone 2",
"shortname": ":punch_tone2:",
"category": "people",
@@ -22158,11 +24765,12 @@
"keywords": [
"fist",
"punch"
- ]
+ ],
+ "moji": "👊🏼"
},
"punch_tone3": {
"unicode": "1F44A-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "fisted hand sign tone 3",
"shortname": ":punch_tone3:",
"category": "people",
@@ -22171,11 +24779,12 @@
"keywords": [
"fist",
"punch"
- ]
+ ],
+ "moji": "👊🏽"
},
"punch_tone4": {
"unicode": "1F44A-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "fisted hand sign tone 4",
"shortname": ":punch_tone4:",
"category": "people",
@@ -22184,11 +24793,12 @@
"keywords": [
"fist",
"punch"
- ]
+ ],
+ "moji": "👊🏾"
},
"punch_tone5": {
"unicode": "1F44A-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "fisted hand sign tone 5",
"shortname": ":punch_tone5:",
"category": "people",
@@ -22197,14 +24807,15 @@
"keywords": [
"fist",
"punch"
- ]
+ ],
+ "moji": "👊🏿"
},
"purple_heart": {
"unicode": "1F49C",
"unicode_alternates": [],
"name": "purple heart",
"shortname": ":purple_heart:",
- "category": "emoticons",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -22215,7 +24826,6 @@
"purple",
"violet",
"heart",
- "love",
"sensitive",
"understanding",
"compassionate",
@@ -22224,7 +24834,8 @@
"honor",
"royalty",
"veteran",
- "sacrifice"
+ "sacrifice",
+ "symbol"
],
"moji": "💜"
},
@@ -22233,7 +24844,7 @@
"unicode_alternates": [],
"name": "purse",
"shortname": ":purse:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -22246,9 +24857,9 @@
"handbag",
"coin bag",
"accessory",
- "money",
"ladies",
- "shopping"
+ "shopping",
+ "women"
],
"moji": "👛"
},
@@ -22261,28 +24872,18 @@
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "stationery"
+ "stationery",
+ "object",
+ "office"
],
"moji": "📌"
},
- "pushpin_black": {
- "unicode": "1F588",
- "unicode_alternates": [],
- "name": "black pushpin",
- "shortname": ":pushpin_black:",
- "category": "objects_symbols",
- "aliases": [],
- "aliases_ascii": [],
- "keywords": [
- "stationery"
- ]
- },
"put_litter_in_its_place": {
"unicode": "1F6AE",
"unicode_alternates": [],
"name": "put litter in its place symbol",
"shortname": ":put_litter_in_its_place:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -22292,7 +24893,8 @@
"trash",
"garbage",
"receptacle",
- "can"
+ "can",
+ "symbol"
],
"moji": "🚮"
},
@@ -22301,12 +24903,15 @@
"unicode_alternates": [],
"name": "black question mark ornament",
"shortname": ":question:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"confused",
- "doubt"
+ "doubt",
+ "symbol",
+ "punctuation",
+ "wth"
],
"moji": "❓"
},
@@ -22320,7 +24925,8 @@
"aliases_ascii": [],
"keywords": [
"animal",
- "nature"
+ "nature",
+ "wildlife"
],
"moji": "🐰"
},
@@ -22339,7 +24945,8 @@
"bunny",
"easter",
"reproduction",
- "prolific"
+ "prolific",
+ "wildlife"
],
"moji": "🐇"
},
@@ -22348,7 +24955,7 @@
"unicode_alternates": [],
"name": "racing car",
"shortname": ":race_car:",
- "category": "activity",
+ "category": "travel",
"aliases": [
":racing_car:"
],
@@ -22359,8 +24966,11 @@
"stock",
"nascar",
"speed",
- "drive"
- ]
+ "drive",
+ "transportation",
+ "car"
+ ],
+ "moji": "🏎"
},
"racehorse": {
"unicode": "1F40E",
@@ -22391,7 +25001,8 @@
"gelding",
"yearling",
"thoroughbred",
- "pony"
+ "pony",
+ "wildlife"
],
"moji": "🐎"
},
@@ -22407,7 +25018,8 @@
"communication",
"music",
"podcast",
- "program"
+ "program",
+ "electronics"
],
"moji": "📻"
},
@@ -22416,17 +25028,19 @@
"unicode_alternates": [],
"name": "radio button",
"shortname": ":radio_button:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "input"
+ "input",
+ "symbol",
+ "circle"
],
"moji": "🔘"
},
"radioactive": {
"unicode": "2622",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "radioactive sign",
"shortname": ":radioactive:",
"category": "symbols",
@@ -22435,15 +25049,17 @@
],
"aliases_ascii": [],
"keywords": [
- "symbol"
- ]
+ "symbol",
+ "science"
+ ],
+ "moji": "☢"
},
"rage": {
"unicode": "1F621",
"unicode_alternates": [],
"name": "pouting face",
"shortname": ":rage:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -22454,7 +25070,9 @@
"pout",
"anger",
"rage",
- "irate"
+ "irate",
+ "smiley",
+ "emotion"
],
"moji": "😡"
},
@@ -22463,7 +25081,7 @@
"unicode_alternates": [],
"name": "railway car",
"shortname": ":railway_car:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -22473,7 +25091,8 @@
"rail",
"car",
"coach",
- "train"
+ "train",
+ "travel"
],
"moji": "🚃"
},
@@ -22482,7 +25101,7 @@
"unicode_alternates": [],
"name": "railway track",
"shortname": ":railway_track:",
- "category": "travel_places",
+ "category": "travel",
"aliases": [
":railroad_track:"
],
@@ -22492,15 +25111,18 @@
"trolley",
"subway",
"locomotive",
- "transit"
- ]
+ "transit",
+ "travel",
+ "vacation"
+ ],
+ "moji": "🛤"
},
"rainbow": {
"unicode": "1F308",
"unicode_alternates": [],
"name": "rainbow",
"shortname": ":rainbow:",
- "category": "nature",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -22516,28 +25138,114 @@
"spectrum",
"refract",
"leprechaun",
- "gold"
+ "gold",
+ "weather",
+ "gay",
+ "rain"
],
"moji": "🌈"
},
+ "raised_back_of_hand": {
+ "unicode": "1F91A",
+ "unicode_alternates": [],
+ "name": "raised back of hand",
+ "shortname": ":raised_back_of_hand:",
+ "category": "people",
+ "aliases": [
+ ":back_of_hand:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤚"
+ },
+ "raised_back_of_hand_tone1": {
+ "unicode": "1F91A-1F3FB",
+ "unicode_alternates": [],
+ "name": "raised back of hand tone 1",
+ "shortname": ":raised_back_of_hand_tone1:",
+ "category": "people",
+ "aliases": [
+ ":back_of_hand_tone1:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤚🏻"
+ },
+ "raised_back_of_hand_tone2": {
+ "unicode": "1F91A-1F3FC",
+ "unicode_alternates": [],
+ "name": "raised back of hand tone 2",
+ "shortname": ":raised_back_of_hand_tone2:",
+ "category": "people",
+ "aliases": [
+ ":back_of_hand_tone2:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤚🏼"
+ },
+ "raised_back_of_hand_tone3": {
+ "unicode": "1F91A-1F3FD",
+ "unicode_alternates": [],
+ "name": "raised back of hand tone 3",
+ "shortname": ":raised_back_of_hand_tone3:",
+ "category": "people",
+ "aliases": [
+ ":back_of_hand_tone3:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤚🏽"
+ },
+ "raised_back_of_hand_tone4": {
+ "unicode": "1F91A-1F3FE",
+ "unicode_alternates": [],
+ "name": "raised back of hand tone 4",
+ "shortname": ":raised_back_of_hand_tone4:",
+ "category": "people",
+ "aliases": [
+ ":back_of_hand_tone4:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤚🏾"
+ },
+ "raised_back_of_hand_tone5": {
+ "unicode": "1F91A-1F3FF",
+ "unicode_alternates": [],
+ "name": "raised back of hand tone 5",
+ "shortname": ":raised_back_of_hand_tone5:",
+ "category": "people",
+ "aliases": [
+ ":back_of_hand_tone5:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤚🏿"
+ },
"raised_hand": {
"unicode": "270B",
"unicode_alternates": [],
"name": "raised hand",
"shortname": ":raised_hand:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"female",
"girl",
- "woman"
+ "woman",
+ "body",
+ "hands",
+ "hi",
+ "diversity",
+ "girls night"
],
"moji": "✋"
},
"raised_hand_tone1": {
"unicode": "270B-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "raised hand tone 1",
"shortname": ":raised_hand_tone1:",
"category": "people",
@@ -22547,11 +25255,12 @@
"female",
"girl",
"woman"
- ]
+ ],
+ "moji": "✋🏻"
},
"raised_hand_tone2": {
"unicode": "270B-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "raised hand tone 2",
"shortname": ":raised_hand_tone2:",
"category": "people",
@@ -22561,11 +25270,12 @@
"female",
"girl",
"woman"
- ]
+ ],
+ "moji": "✋🏼"
},
"raised_hand_tone3": {
"unicode": "270B-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "raised hand tone 3",
"shortname": ":raised_hand_tone3:",
"category": "people",
@@ -22575,11 +25285,12 @@
"female",
"girl",
"woman"
- ]
+ ],
+ "moji": "✋🏽"
},
"raised_hand_tone4": {
"unicode": "270B-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "raised hand tone 4",
"shortname": ":raised_hand_tone4:",
"category": "people",
@@ -22589,11 +25300,12 @@
"female",
"girl",
"woman"
- ]
+ ],
+ "moji": "✋🏾"
},
"raised_hand_tone5": {
"unicode": "270B-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "raised hand tone 5",
"shortname": ":raised_hand_tone5:",
"category": "people",
@@ -22603,14 +25315,15 @@
"female",
"girl",
"woman"
- ]
+ ],
+ "moji": "✋🏿"
},
"raised_hands": {
"unicode": "1F64C",
"unicode_alternates": [],
"name": "person raising both hands in celebration",
"shortname": ":raised_hands:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -22619,13 +25332,19 @@
"winning",
"woot",
"yay",
- "banzai"
+ "banzai",
+ "body",
+ "hands",
+ "diversity",
+ "perfect",
+ "good",
+ "parties"
],
"moji": "🙌"
},
"raised_hands_tone1": {
"unicode": "1F64C-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person raising both hands in celebration tone 1",
"shortname": ":raised_hands_tone1:",
"category": "people",
@@ -22639,11 +25358,12 @@
"yay",
"banzai",
"raised"
- ]
+ ],
+ "moji": "🙌🏻"
},
"raised_hands_tone2": {
"unicode": "1F64C-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person raising both hands in celebration tone 2",
"shortname": ":raised_hands_tone2:",
"category": "people",
@@ -22657,11 +25377,12 @@
"yay",
"banzai",
"raised"
- ]
+ ],
+ "moji": "🙌🏼"
},
"raised_hands_tone3": {
"unicode": "1F64C-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person raising both hands in celebration tone 3",
"shortname": ":raised_hands_tone3:",
"category": "people",
@@ -22675,11 +25396,12 @@
"yay",
"banzai",
"raised"
- ]
+ ],
+ "moji": "🙌🏽"
},
"raised_hands_tone4": {
"unicode": "1F64C-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person raising both hands in celebration tone 4",
"shortname": ":raised_hands_tone4:",
"category": "people",
@@ -22693,11 +25415,12 @@
"yay",
"banzai",
"raised"
- ]
+ ],
+ "moji": "🙌🏾"
},
"raised_hands_tone5": {
"unicode": "1F64C-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "person raising both hands in celebration tone 5",
"shortname": ":raised_hands_tone5:",
"category": "people",
@@ -22711,14 +25434,15 @@
"yay",
"banzai",
"raised"
- ]
+ ],
+ "moji": "🙌🏿"
},
"raising_hand": {
"unicode": "1F64B",
"unicode_alternates": [],
"name": "happy person raising one hand",
"shortname": ":raising_hand:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -22729,13 +25453,16 @@
"raise",
"notice",
"attention",
- "answer"
+ "answer",
+ "people",
+ "women",
+ "diversity"
],
"moji": "🙋"
},
"raising_hand_tone1": {
"unicode": "1F64B-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "happy person raising one hand tone1",
"shortname": ":raising_hand_tone1:",
"category": "people",
@@ -22749,11 +25476,12 @@
"notice",
"attention",
"answer"
- ]
+ ],
+ "moji": "🙋🏻"
},
"raising_hand_tone2": {
"unicode": "1F64B-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "happy person raising one hand tone2",
"shortname": ":raising_hand_tone2:",
"category": "people",
@@ -22767,11 +25495,12 @@
"notice",
"attention",
"answer"
- ]
+ ],
+ "moji": "🙋🏼"
},
"raising_hand_tone3": {
"unicode": "1F64B-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "happy person raising one hand tone3",
"shortname": ":raising_hand_tone3:",
"category": "people",
@@ -22785,11 +25514,12 @@
"notice",
"attention",
"answer"
- ]
+ ],
+ "moji": "🙋🏽"
},
"raising_hand_tone4": {
"unicode": "1F64B-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "happy person raising one hand tone4",
"shortname": ":raising_hand_tone4:",
"category": "people",
@@ -22803,11 +25533,12 @@
"notice",
"attention",
"answer"
- ]
+ ],
+ "moji": "🙋🏾"
},
"raising_hand_tone5": {
"unicode": "1F64B-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "happy person raising one hand tone5",
"shortname": ":raising_hand_tone5:",
"category": "people",
@@ -22821,7 +25552,8 @@
"notice",
"attention",
"answer"
- ]
+ ],
+ "moji": "🙋🏿"
},
"ram": {
"unicode": "1F40F",
@@ -22836,10 +25568,10 @@
"nature",
"sheep",
"ram",
- "sheep",
"male",
"horn",
- "horns"
+ "horns",
+ "wildlife"
],
"moji": "🐏"
},
@@ -22848,7 +25580,7 @@
"unicode_alternates": [],
"name": "steaming bowl",
"shortname": ":ramen:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -22860,7 +25592,8 @@
"noodles",
"bowl",
"steaming",
- "soup"
+ "soup",
+ "japan"
],
"moji": "🍜"
},
@@ -22884,7 +25617,7 @@
},
"record_button": {
"unicode": "23FA",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "black circle for record",
"shortname": ":record_button:",
"category": "symbols",
@@ -22892,8 +25625,10 @@
"aliases_ascii": [],
"keywords": [
"sound",
- "symbol"
- ]
+ "symbol",
+ "circle"
+ ],
+ "moji": "⏺"
},
"recycle": {
"unicode": "267B",
@@ -22902,14 +25637,15 @@
],
"name": "black universal recycling symbol",
"shortname": ":recycle:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"arrow",
"environment",
"garbage",
- "trash"
+ "trash",
+ "symbol"
],
"moji": "♻"
},
@@ -22918,12 +25654,14 @@
"unicode_alternates": [],
"name": "automobile",
"shortname": ":red_car:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"transportation",
- "vehicle"
+ "vehicle",
+ "car",
+ "travel"
],
"moji": "🚗"
},
@@ -22932,11 +25670,14 @@
"unicode_alternates": [],
"name": "large red circle",
"shortname": ":red_circle:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "shape"
+ "shape",
+ "shapes",
+ "symbol",
+ "circle"
],
"moji": "🔴"
},
@@ -22946,12 +25687,13 @@
"unicode_alternates": [],
"name": "registered sign",
"shortname": ":registered:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"alphabet",
- "circle"
+ "circle",
+ "symbol"
]
},
"relaxed": {
@@ -22961,7 +25703,7 @@
],
"name": "white smiling face",
"shortname": ":relaxed:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -22969,7 +25711,9 @@
"face",
"happiness",
"massage",
- "smile"
+ "smile",
+ "happy",
+ "smiley"
],
"moji": "☺"
},
@@ -22978,7 +25722,7 @@
"unicode_alternates": [],
"name": "relieved face",
"shortname": ":relieved:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -22989,8 +25733,9 @@
"relaxed",
"relieved",
"satisfied",
- "phew",
- "relief"
+ "relief",
+ "smiley",
+ "emotion"
],
"moji": "😌"
},
@@ -22999,24 +25744,28 @@
"unicode_alternates": [],
"name": "reminder ribbon",
"shortname": ":reminder_ribbon:",
- "category": "celebration",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "awareness"
- ]
+ "awareness",
+ "award"
+ ],
+ "moji": "🎗"
},
"repeat": {
"unicode": "1F501",
"unicode_alternates": [],
"name": "clockwise rightwards and leftwards open circle arr",
"shortname": ":repeat:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"loop",
- "record"
+ "record",
+ "arrow",
+ "symbol"
],
"moji": "🔁"
},
@@ -23025,12 +25774,14 @@
"unicode_alternates": [],
"name": "clockwise rightwards and leftwards open circle arr",
"shortname": ":repeat_one:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"blue-square",
- "loop"
+ "loop",
+ "arrow",
+ "symbol"
],
"moji": "🔂"
},
@@ -23039,7 +25790,7 @@
"unicode_alternates": [],
"name": "restroom",
"shortname": ":restroom:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -23051,7 +25802,8 @@
"restroom",
"sign",
"shared",
- "toilet"
+ "toilet",
+ "symbol"
],
"moji": "🚻"
},
@@ -23060,7 +25812,7 @@
"unicode_alternates": [],
"name": "revolving hearts",
"shortname": ":revolving_hearts:",
- "category": "emoticons",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -23074,7 +25826,8 @@
"moving",
"circle",
"multiple",
- "lovers"
+ "lovers",
+ "symbol"
],
"moji": "💞"
},
@@ -23083,21 +25836,36 @@
"unicode_alternates": [],
"name": "black left-pointing double triangle",
"shortname": ":rewind:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"blue-square",
- "play"
+ "play",
+ "arrow",
+ "symbol"
],
"moji": "⏪"
},
+ "rhino": {
+ "unicode": "1F98F",
+ "unicode_alternates": [],
+ "name": "rhinoceros",
+ "shortname": ":rhino:",
+ "category": "nature",
+ "aliases": [
+ ":rhinoceros:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🦏"
+ },
"ribbon": {
"unicode": "1F380",
"unicode_alternates": [],
"name": "ribbon",
"shortname": ":ribbon:",
- "category": "emoticons",
+ "category": "objects",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -23108,7 +25876,10 @@
"ribbon",
"lace",
"wrap",
- "decorate"
+ "decorate",
+ "object",
+ "gift",
+ "birthday"
],
"moji": "🎀"
},
@@ -23117,7 +25888,7 @@
"unicode_alternates": [],
"name": "cooked rice",
"shortname": ":rice:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -23125,8 +25896,9 @@
"rice",
"white",
"grain",
- "food",
- "bowl"
+ "bowl",
+ "sushi",
+ "japan"
],
"moji": "🍚"
},
@@ -23135,7 +25907,7 @@
"unicode_alternates": [],
"name": "rice ball",
"shortname": ":rice_ball:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -23146,7 +25918,8 @@
"white",
"nori",
"seaweed",
- "japanese"
+ "sushi",
+ "japan"
],
"moji": "🍙"
},
@@ -23155,7 +25928,7 @@
"unicode_alternates": [],
"name": "rice cracker",
"shortname": ":rice_cracker:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -23164,8 +25937,7 @@
"rice",
"cracker",
"seaweed",
- "food",
- "japanese"
+ "sushi"
],
"moji": "🍘"
},
@@ -23174,7 +25946,7 @@
"unicode_alternates": [],
"name": "moon viewing ceremony",
"shortname": ":rice_scene:",
- "category": "objects",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -23187,92 +25959,115 @@
"rice",
"scene",
"festival",
- "autumn"
+ "autumn",
+ "places",
+ "space",
+ "sky",
+ "travel"
],
"moji": "🎑"
},
- "right_speaker": {
- "unicode": "1F568",
+ "right_facing_fist": {
+ "unicode": "1F91C",
"unicode_alternates": [],
- "name": "right speaker",
- "shortname": ":right_speaker:",
- "category": "objects_symbols",
- "aliases": [],
+ "name": "right-facing fist",
+ "shortname": ":right_facing_fist:",
+ "category": "people",
+ "aliases": [
+ ":right_fist:"
+ ],
"aliases_ascii": [],
- "keywords": [
- "sound",
- "listen",
- "hear",
- "noise",
- "volume"
- ]
+ "keywords": [],
+ "moji": "🤜"
},
- "right_speaker_one": {
- "unicode": "1F569",
+ "right_facing_fist_tone1": {
+ "unicode": "1F91C-1F3FB",
"unicode_alternates": [],
- "name": "right speaker with one sound wave",
- "shortname": ":right_speaker_one:",
- "category": "objects_symbols",
+ "name": "right facing fist tone 1",
+ "shortname": ":right_facing_fist_tone1:",
+ "category": "people",
"aliases": [
- ":right_speaker_with_one_sound_wave:"
+ ":right_fist_tone1:"
],
"aliases_ascii": [],
- "keywords": [
- "low",
- "volume"
- ]
+ "keywords": [],
+ "moji": "🤜🏻"
},
- "right_speaker_three": {
- "unicode": "1F56A",
+ "right_facing_fist_tone2": {
+ "unicode": "1F91C-1F3FC",
"unicode_alternates": [],
- "name": "right speaker with three sound waves",
- "shortname": ":right_speaker_three:",
- "category": "objects_symbols",
+ "name": "right facing fist tone 2",
+ "shortname": ":right_facing_fist_tone2:",
+ "category": "people",
"aliases": [
- ":right_speaker_with_three_sound_waves:"
+ ":right_fist_tone2:"
],
"aliases_ascii": [],
- "keywords": [
- "loud",
- "high",
- "volume"
- ]
+ "keywords": [],
+ "moji": "🤜🏼"
+ },
+ "right_facing_fist_tone3": {
+ "unicode": "1F91C-1F3FD",
+ "unicode_alternates": [],
+ "name": "right facing fist tone 3",
+ "shortname": ":right_facing_fist_tone3:",
+ "category": "people",
+ "aliases": [
+ ":right_fist_tone3:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤜🏽"
+ },
+ "right_facing_fist_tone4": {
+ "unicode": "1F91C-1F3FE",
+ "unicode_alternates": [],
+ "name": "right facing fist tone 4",
+ "shortname": ":right_facing_fist_tone4:",
+ "category": "people",
+ "aliases": [
+ ":right_fist_tone4:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤜🏾"
+ },
+ "right_facing_fist_tone5": {
+ "unicode": "1F91C-1F3FF",
+ "unicode_alternates": [],
+ "name": "right facing fist tone 5",
+ "shortname": ":right_facing_fist_tone5:",
+ "category": "people",
+ "aliases": [
+ ":right_fist_tone5:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤜🏿"
},
"ring": {
"unicode": "1F48D",
"unicode_alternates": [],
"name": "ring",
"shortname": ":ring:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"marriage",
"propose",
"valentines",
- "wedding"
+ "wedding",
+ "object",
+ "fashion",
+ "gem",
+ "accessories"
],
"moji": "💍"
},
- "ringing_bell": {
- "unicode": "1F56D",
- "unicode_alternates": [],
- "name": "ringing bell",
- "shortname": ":ringing_bell:",
- "category": "objects_symbols",
- "aliases": [],
- "aliases_ascii": [],
- "keywords": [
- "alert",
- "ding",
- "volume",
- "sound",
- "chime"
- ]
- },
"robot": {
"unicode": "1F916",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "robot face",
"shortname": ":robot:",
"category": "people",
@@ -23280,14 +26075,18 @@
":robot_face:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "monster",
+ "robot"
+ ],
+ "moji": "🤖"
},
"rocket": {
"unicode": "1F680",
"unicode_alternates": [],
"name": "rocket",
"shortname": ":rocket:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -23298,16 +26097,33 @@
"space",
"spacecraft",
"astronaut",
- "cosmonaut"
+ "cosmonaut",
+ "transportation",
+ "object",
+ "fly",
+ "blast"
],
"moji": "🚀"
},
+ "rofl": {
+ "unicode": "1F923",
+ "unicode_alternates": [],
+ "name": "rolling on the floor laughing",
+ "shortname": ":rofl:",
+ "category": "people",
+ "aliases": [
+ ":rolling_on_the_floor_laughing:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤣"
+ },
"roller_coaster": {
"unicode": "1F3A2",
"unicode_alternates": [],
"name": "roller coaster",
"shortname": ":roller_coaster:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -23322,13 +26138,16 @@
"park",
"fair",
"ride",
- "entertainment"
+ "entertainment",
+ "places",
+ "vacation",
+ "roller coaster"
],
"moji": "🎢"
},
"rolling_eyes": {
"unicode": "1F644",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "face with rolling eyes",
"shortname": ":rolling_eyes:",
"category": "people",
@@ -23336,7 +26155,14 @@
":face_with_rolling_eyes:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "mad",
+ "smiley",
+ "rolling eyes",
+ "emotion",
+ "sarcastic"
+ ],
+ "moji": "🙄"
},
"rooster": {
"unicode": "1F413",
@@ -23375,9 +26201,13 @@
"fragrant",
"flower",
"thorns",
- "love",
"petals",
- "romance"
+ "romance",
+ "nature",
+ "plant",
+ "rip",
+ "condolence",
+ "beautiful"
],
"moji": "🌹"
},
@@ -23386,31 +26216,21 @@
"unicode_alternates": [],
"name": "rosette",
"shortname": ":rosette:",
- "category": "objects_symbols",
- "aliases": [],
- "aliases_ascii": [],
- "keywords": [
- "flower"
- ]
- },
- "rosette_black": {
- "unicode": "1F3F6",
- "unicode_alternates": [],
- "name": "black rosette",
- "shortname": ":rosette_black:",
- "category": "objects_symbols",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "flower"
- ]
+ "flower",
+ "tropical"
+ ],
+ "moji": "🏵"
},
"rotating_light": {
"unicode": "1F6A8",
"unicode_alternates": [],
"name": "police cars revolving light",
"shortname": ":rotating_light:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -23419,8 +26239,8 @@
"emergency",
"police",
"light",
- "police",
- "emergency"
+ "transportation",
+ "object"
],
"moji": "🚨"
},
@@ -23429,11 +26249,13 @@
"unicode_alternates": [],
"name": "round pushpin",
"shortname": ":round_pushpin:",
- "category": "places",
+ "category": "objects",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "stationery"
+ "stationery",
+ "object",
+ "office"
],
"moji": "📍"
},
@@ -23442,7 +26264,7 @@
"unicode_alternates": [],
"name": "rowboat",
"shortname": ":rowboat:",
- "category": "places",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -23453,13 +26275,18 @@
"boat",
"row",
"oar",
- "paddle"
+ "paddle",
+ "men",
+ "workout",
+ "sport",
+ "rowing",
+ "diversity"
],
"moji": "🚣"
},
"rowboat_tone1": {
"unicode": "1F6A3-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "rowboat tone 1",
"shortname": ":rowboat_tone1:",
"category": "activity",
@@ -23473,11 +26300,12 @@
"row",
"oar",
"paddle"
- ]
+ ],
+ "moji": "🚣🏻"
},
"rowboat_tone2": {
"unicode": "1F6A3-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "rowboat tone 2",
"shortname": ":rowboat_tone2:",
"category": "activity",
@@ -23491,11 +26319,12 @@
"row",
"oar",
"paddle"
- ]
+ ],
+ "moji": "🚣🏼"
},
"rowboat_tone3": {
"unicode": "1F6A3-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "rowboat tone 3",
"shortname": ":rowboat_tone3:",
"category": "activity",
@@ -23509,11 +26338,12 @@
"row",
"oar",
"paddle"
- ]
+ ],
+ "moji": "🚣🏽"
},
"rowboat_tone4": {
"unicode": "1F6A3-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "rowboat tone 4",
"shortname": ":rowboat_tone4:",
"category": "activity",
@@ -23527,11 +26357,12 @@
"row",
"oar",
"paddle"
- ]
+ ],
+ "moji": "🚣🏾"
},
"rowboat_tone5": {
"unicode": "1F6A3-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "rowboat tone 5",
"shortname": ":rowboat_tone5:",
"category": "activity",
@@ -23545,14 +26376,15 @@
"row",
"oar",
"paddle"
- ]
+ ],
+ "moji": "🚣🏿"
},
"rugby_football": {
"unicode": "1F3C9",
"unicode_alternates": [],
"name": "rugby football",
"shortname": ":rugby_football:",
- "category": "objects",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -23562,7 +26394,8 @@
"ball",
"sport",
"team",
- "england"
+ "england",
+ "game"
],
"moji": "🏉"
},
@@ -23571,7 +26404,7 @@
"unicode_alternates": [],
"name": "runner",
"shortname": ":runner:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -23581,16 +26414,19 @@
"run",
"runner",
"jog",
- "exercise",
"sprint",
"race",
- "dash"
+ "dash",
+ "people",
+ "men",
+ "diversity",
+ "boys night"
],
"moji": "🏃"
},
"runner_tone1": {
"unicode": "1F3C3-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "runner tone 1",
"shortname": ":runner_tone1:",
"category": "people",
@@ -23605,11 +26441,12 @@
"race",
"dash",
"marathon"
- ]
+ ],
+ "moji": "🏃🏻"
},
"runner_tone2": {
"unicode": "1F3C3-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "runner tone 2",
"shortname": ":runner_tone2:",
"category": "people",
@@ -23624,11 +26461,12 @@
"race",
"dash",
"marathon"
- ]
+ ],
+ "moji": "🏃🏼"
},
"runner_tone3": {
"unicode": "1F3C3-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "runner tone 3",
"shortname": ":runner_tone3:",
"category": "people",
@@ -23643,11 +26481,12 @@
"race",
"dash",
"marathon"
- ]
+ ],
+ "moji": "🏃🏽"
},
"runner_tone4": {
"unicode": "1F3C3-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "runner tone 4",
"shortname": ":runner_tone4:",
"category": "people",
@@ -23662,11 +26501,12 @@
"race",
"dash",
"marathon"
- ]
+ ],
+ "moji": "🏃🏾"
},
"runner_tone5": {
"unicode": "1F3C3-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "runner tone 5",
"shortname": ":runner_tone5:",
"category": "people",
@@ -23681,14 +26521,15 @@
"race",
"dash",
"marathon"
- ]
+ ],
+ "moji": "🏃🏿"
},
"running_shirt_with_sash": {
"unicode": "1F3BD",
"unicode_alternates": [],
"name": "running shirt with sash",
"shortname": ":running_shirt_with_sash:",
- "category": "emoticons",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -23699,13 +26540,14 @@
"shirt",
"cloths",
"compete",
- "sports"
+ "sports",
+ "award"
],
"moji": "🎽"
},
"sa": {
"unicode": "1F202",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "squared katakana sa",
"shortname": ":sa:",
"category": "symbols",
@@ -23716,7 +26558,8 @@
"japanese",
"symbol",
"word"
- ]
+ ],
+ "moji": "🈂"
},
"sagittarius": {
"unicode": "2650",
@@ -23725,7 +26568,7 @@
],
"name": "sagittarius",
"shortname": ":sagittarius:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -23738,9 +26581,8 @@
"stars",
"zodiac",
"sign",
- "sign",
- "zodiac",
- "horoscope"
+ "horoscope",
+ "symbol"
],
"moji": "♐"
},
@@ -23751,12 +26593,15 @@
],
"name": "sailboat",
"shortname": ":sailboat:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"ship",
- "transportation"
+ "transportation",
+ "travel",
+ "boat",
+ "vacation"
],
"moji": "⛵"
},
@@ -23765,7 +26610,7 @@
"unicode_alternates": [],
"name": "sake bottle and cup",
"shortname": ":sake:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -23774,26 +26619,41 @@
"drunk",
"wine",
"sake",
- "wine",
"rice",
"ferment",
"alcohol",
"japanese",
- "drink"
+ "japan",
+ "girls night"
],
"moji": "🍶"
},
+ "salad": {
+ "unicode": "1F957",
+ "unicode_alternates": [],
+ "name": "green salad",
+ "shortname": ":salad:",
+ "category": "food",
+ "aliases": [
+ ":green_salad:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🥗"
+ },
"sandal": {
"unicode": "1F461",
"unicode_alternates": [],
"name": "womans sandal",
"shortname": ":sandal:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"fashion",
- "shoes"
+ "shoes",
+ "shoe",
+ "accessories"
],
"moji": "👡"
},
@@ -23802,7 +26662,7 @@
"unicode_alternates": [],
"name": "father christmas",
"shortname": ":santa:",
- "category": "objects",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -23823,14 +26683,18 @@
"nice",
"sleigh",
"father",
- "christmas",
- "holiday"
+ "holiday",
+ "people",
+ "hat",
+ "winter",
+ "holidays",
+ "diversity"
],
"moji": "🎅"
},
"santa_tone1": {
"unicode": "1F385-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "father christmas tone 1",
"shortname": ":santa_tone1:",
"category": "people",
@@ -23852,11 +26716,12 @@
"nice",
"sleigh",
"holiday"
- ]
+ ],
+ "moji": "🎅🏻"
},
"santa_tone2": {
"unicode": "1F385-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "father christmas tone 2",
"shortname": ":santa_tone2:",
"category": "people",
@@ -23878,11 +26743,12 @@
"nice",
"sleigh",
"holiday"
- ]
+ ],
+ "moji": "🎅🏼"
},
"santa_tone3": {
"unicode": "1F385-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "father christmas tone 3",
"shortname": ":santa_tone3:",
"category": "people",
@@ -23904,11 +26770,12 @@
"nice",
"sleigh",
"holiday"
- ]
+ ],
+ "moji": "🎅🏽"
},
"santa_tone4": {
"unicode": "1F385-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "father christmas tone 4",
"shortname": ":santa_tone4:",
"category": "people",
@@ -23930,11 +26797,12 @@
"nice",
"sleigh",
"holiday"
- ]
+ ],
+ "moji": "🎅🏾"
},
"santa_tone5": {
"unicode": "1F385-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "father christmas tone 5",
"shortname": ":santa_tone5:",
"category": "people",
@@ -23956,7 +26824,8 @@
"nice",
"sleigh",
"holiday"
- ]
+ ],
+ "moji": "🎅🏿"
},
"satellite": {
"unicode": "1F4E1",
@@ -23967,7 +26836,8 @@
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "communication"
+ "communication",
+ "object"
],
"moji": "📡"
},
@@ -23976,21 +26846,23 @@
"unicode_alternates": [],
"name": "satellite",
"shortname": ":satellite_orbital:",
- "category": "objects_symbols",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"communication",
"orbital",
- "space"
- ]
+ "space",
+ "object"
+ ],
+ "moji": "🛰"
},
"saxophone": {
"unicode": "1F3B7",
"unicode_alternates": [],
"name": "saxophone",
"shortname": ":saxophone:",
- "category": "objects",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -23998,15 +26870,14 @@
"music",
"saxophone",
"sax",
- "music",
- "instrument",
- "woodwind"
+ "woodwind",
+ "instruments"
],
"moji": "🎷"
},
"scales": {
"unicode": "2696",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "scales",
"shortname": ":scales:",
"category": "objects",
@@ -24020,14 +26891,15 @@
"tool",
"weight",
"zodiac"
- ]
+ ],
+ "moji": "⚖"
},
"school": {
"unicode": "1F3EB",
"unicode_alternates": [],
"name": "school",
"shortname": ":school:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -24039,7 +26911,8 @@
"high",
"college",
"teach",
- "education"
+ "education",
+ "places"
],
"moji": "🏫"
},
@@ -24048,7 +26921,7 @@
"unicode_alternates": [],
"name": "school satchel",
"shortname": ":school_satchel:",
- "category": "objects",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -24058,14 +26931,16 @@
"school",
"satchel",
"backpack",
- "bag",
"packing",
"pack",
"hike",
- "education",
"adventure",
"travel",
- "sightsee"
+ "sightsee",
+ "fashion",
+ "office",
+ "vacation",
+ "accessories"
],
"moji": "🎒"
},
@@ -24081,19 +26956,39 @@
"aliases_ascii": [],
"keywords": [
"cut",
- "stationery"
+ "stationery",
+ "object",
+ "tool",
+ "weapon",
+ "office"
],
"moji": "✂"
},
+ "scooter": {
+ "unicode": "1F6F4",
+ "unicode_alternates": [],
+ "name": "scooter",
+ "shortname": ":scooter:",
+ "category": "travel",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🛴"
+ },
"scorpion": {
"unicode": "1F982",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "scorpion",
"shortname": ":scorpion:",
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "insects",
+ "reptile",
+ "animal"
+ ],
+ "moji": "🦂"
},
"scorpius": {
"unicode": "264F",
@@ -24102,7 +26997,7 @@
],
"name": "scorpius",
"shortname": ":scorpius:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -24115,9 +27010,8 @@
"stars",
"zodiac",
"sign",
- "sign",
- "zodiac",
- "horoscope"
+ "horoscope",
+ "symbol"
],
"moji": "♏"
},
@@ -24126,7 +27020,7 @@
"unicode_alternates": [],
"name": "face screaming in fear",
"shortname": ":scream:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -24135,7 +27029,12 @@
"scream",
"painting",
"artist",
- "alien"
+ "alien",
+ "smiley",
+ "surprised",
+ "wow",
+ "emotion",
+ "omg"
],
"moji": "😱"
},
@@ -24144,7 +27043,7 @@
"unicode_alternates": [],
"name": "weary cat face",
"shortname": ":scream_cat:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -24161,7 +27060,8 @@
"exhausted",
"scream",
"painting",
- "artist"
+ "artist",
+ "cat"
],
"moji": "🙀"
},
@@ -24174,7 +27074,9 @@
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "documents"
+ "documents",
+ "object",
+ "office"
],
"moji": "📜"
},
@@ -24183,14 +27085,31 @@
"unicode_alternates": [],
"name": "seat",
"shortname": ":seat:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "sit"
+ "sit",
+ "transportation",
+ "object",
+ "travel",
+ "vacation"
],
"moji": "💺"
},
+ "second_place": {
+ "unicode": "1F948",
+ "unicode_alternates": [],
+ "name": "second place medal",
+ "shortname": ":second_place:",
+ "category": "activity",
+ "aliases": [
+ ":second_place_medal:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🥈"
+ },
"secret": {
"unicode": "3299",
"unicode_alternates": [
@@ -24198,11 +27117,13 @@
],
"name": "circled ideograph secret",
"shortname": ":secret:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "privacy"
+ "privacy",
+ "japan",
+ "symbol"
],
"moji": "㊙"
},
@@ -24211,14 +27132,13 @@
"unicode_alternates": [],
"name": "see-no-evil monkey",
"shortname": ":see_no_evil:",
- "category": "emoticons",
+ "category": "nature",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"animal",
"monkey",
"nature",
- "monkey",
"see",
"eyes",
"vision",
@@ -24241,34 +27161,118 @@
"nature",
"plant",
"seedling",
- "plant",
"new",
"start",
- "grow"
+ "grow",
+ "leaf"
],
"moji": "🌱"
},
+ "selfie": {
+ "unicode": "1F933",
+ "unicode_alternates": [],
+ "name": "selfie",
+ "shortname": ":selfie:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤳"
+ },
+ "selfie_tone1": {
+ "unicode": "1F933-1F3FB",
+ "unicode_alternates": [],
+ "name": "selfie tone 1",
+ "shortname": ":selfie_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤳🏻"
+ },
+ "selfie_tone2": {
+ "unicode": "1F933-1F3FC",
+ "unicode_alternates": [],
+ "name": "selfie tone 2",
+ "shortname": ":selfie_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤳🏼"
+ },
+ "selfie_tone3": {
+ "unicode": "1F933-1F3FD",
+ "unicode_alternates": [],
+ "name": "selfie tone 3",
+ "shortname": ":selfie_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤳🏽"
+ },
+ "selfie_tone4": {
+ "unicode": "1F933-1F3FE",
+ "unicode_alternates": [],
+ "name": "selfie tone 4",
+ "shortname": ":selfie_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤳🏾"
+ },
+ "selfie_tone5": {
+ "unicode": "1F933-1F3FF",
+ "unicode_alternates": [],
+ "name": "selfie tone 5",
+ "shortname": ":selfie_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤳🏿"
+ },
"seven": {
"moji": "7️⃣",
"unicode": "0037-20E3",
"unicode_alternates": [
"0037-FE0F-20E3"
],
- "name": "digit seven",
+ "name": "keycap digit seven",
"shortname": ":seven:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"7",
"blue-square",
"numbers",
- "prime"
+ "prime",
+ "number",
+ "math",
+ "symbol"
]
},
+ "shallow_pan_of_food": {
+ "unicode": "1F958",
+ "unicode_alternates": [],
+ "name": "shallow pan of food",
+ "shortname": ":shallow_pan_of_food:",
+ "category": "food",
+ "aliases": [
+ ":paella:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "pan of food"
+ ],
+ "moji": "🥘"
+ },
"shamrock": {
"unicode": "2618",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "shamrock",
"shortname": ":shamrock:",
"category": "nature",
@@ -24276,15 +27280,29 @@
"aliases_ascii": [],
"keywords": [
"nature",
- "plant"
- ]
+ "plant",
+ "luck",
+ "leaf"
+ ],
+ "moji": "☘"
+ },
+ "shark": {
+ "unicode": "1F988",
+ "unicode_alternates": [],
+ "name": "shark",
+ "shortname": ":shark:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🦈"
},
"shaved_ice": {
"unicode": "1F367",
"unicode_alternates": [],
"name": "shaved ice",
"shortname": ":shaved_ice:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -24295,7 +27313,8 @@
"dessert",
"treat",
"syrup",
- "flavoring"
+ "flavoring",
+ "food"
],
"moji": "🍧"
},
@@ -24334,7 +27353,6 @@
"sea",
"shell",
"spiral",
- "beach",
"sand",
"crab",
"nautilus"
@@ -24346,7 +27364,7 @@
"unicode_alternates": [],
"name": "shield",
"shortname": ":shield:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -24354,12 +27372,13 @@
"route",
"sign",
"highway",
- "interstate"
- ]
+ "object"
+ ],
+ "moji": "🛡"
},
"shinto_shrine": {
"unicode": "26E9",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "shinto shrine",
"shortname": ":shinto_shrine:",
"category": "travel",
@@ -24367,15 +27386,20 @@
"aliases_ascii": [],
"keywords": [
"religion",
- "symbol"
- ]
+ "symbol",
+ "places",
+ "building",
+ "travel",
+ "vacation"
+ ],
+ "moji": "⛩"
},
"ship": {
"unicode": "1F6A2",
"unicode_alternates": [],
"name": "ship",
"shortname": ":ship:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -24383,7 +27407,9 @@
"transportation",
"ferry",
"ship",
- "boat"
+ "boat",
+ "travel",
+ "vacation"
],
"moji": "🚢"
},
@@ -24392,7 +27418,7 @@
"unicode_alternates": [],
"name": "t-shirt",
"shortname": ":shirt:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -24406,7 +27432,7 @@
"unicode_alternates": [],
"name": "shopping bags",
"shortname": ":shopping_bags:",
- "category": "travel_places",
+ "category": "objects",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -24414,8 +27440,25 @@
"mall",
"buy",
"store",
- "shop"
- ]
+ "shop",
+ "object",
+ "birthday",
+ "parties"
+ ],
+ "moji": "🛍"
+ },
+ "shopping_cart": {
+ "unicode": "1F6D2",
+ "unicode_alternates": [],
+ "name": "shopping trolley",
+ "shortname": ":shopping_cart:",
+ "category": "objects",
+ "aliases": [
+ ":shopping_trolley:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🛒"
},
"shower": {
"unicode": "1F6BF",
@@ -24433,22 +27476,100 @@
"shower",
"soap",
"water",
- "clean",
"shampoo",
- "lather"
+ "lather",
+ "object"
],
"moji": "🚿"
},
+ "shrimp": {
+ "unicode": "1F990",
+ "unicode_alternates": [],
+ "name": "shrimp",
+ "shortname": ":shrimp:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🦐"
+ },
+ "shrug": {
+ "unicode": "1F937",
+ "unicode_alternates": [],
+ "name": "shrug",
+ "shortname": ":shrug:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤷"
+ },
+ "shrug_tone1": {
+ "unicode": "1F937-1F3FB",
+ "unicode_alternates": [],
+ "name": "shrug tone 1",
+ "shortname": ":shrug_tone1:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤷🏻"
+ },
+ "shrug_tone2": {
+ "unicode": "1F937-1F3FC",
+ "unicode_alternates": [],
+ "name": "shrug tone 2",
+ "shortname": ":shrug_tone2:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤷🏼"
+ },
+ "shrug_tone3": {
+ "unicode": "1F937-1F3FD",
+ "unicode_alternates": [],
+ "name": "shrug tone 3",
+ "shortname": ":shrug_tone3:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤷🏽"
+ },
+ "shrug_tone4": {
+ "unicode": "1F937-1F3FE",
+ "unicode_alternates": [],
+ "name": "shrug tone 4",
+ "shortname": ":shrug_tone4:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤷🏾"
+ },
+ "shrug_tone5": {
+ "unicode": "1F937-1F3FF",
+ "unicode_alternates": [],
+ "name": "shrug tone 5",
+ "shortname": ":shrug_tone5:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤷🏿"
+ },
"signal_strength": {
"unicode": "1F4F6",
"unicode_alternates": [],
"name": "antenna with bars",
"shortname": ":signal_strength:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "blue-square"
+ "blue-square",
+ "symbol"
],
"moji": "📶"
},
@@ -24458,15 +27579,18 @@
"unicode_alternates": [
"0036-FE0F-20E3"
],
- "name": "digit six",
+ "name": "keycap digit six",
"shortname": ":six:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"6",
"blue-square",
- "numbers"
+ "numbers",
+ "number",
+ "math",
+ "symbol"
]
},
"six_pointed_star": {
@@ -24474,11 +27598,15 @@
"unicode_alternates": [],
"name": "six pointed star with middle dot",
"shortname": ":six_pointed_star:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "purple-square"
+ "purple-square",
+ "religion",
+ "jew",
+ "star",
+ "symbol"
],
"moji": "🔯"
},
@@ -24487,7 +27615,7 @@
"unicode_alternates": [],
"name": "ski and ski boot",
"shortname": ":ski:",
- "category": "objects",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -24499,18 +27627,19 @@
"cross-country",
"poles",
"snow",
- "winter",
"mountain",
"alpine",
"powder",
"slalom",
- "freestyle"
+ "freestyle",
+ "sport",
+ "skiing"
],
"moji": "🎿"
},
"skier": {
"unicode": "26F7",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "skier",
"shortname": ":skier:",
"category": "activity",
@@ -24521,15 +27650,20 @@
"ski",
"snow",
"sport",
- "travel"
- ]
+ "travel",
+ "hat",
+ "vacation",
+ "cold",
+ "skiing"
+ ],
+ "moji": "⛷"
},
"skull": {
"unicode": "1F480",
"unicode_alternates": [],
"name": "skull",
"shortname": ":skull:",
- "category": "emoticons",
+ "category": "people",
"aliases": [
":skeleton:"
],
@@ -24537,13 +27671,15 @@
"keywords": [
"dead",
"skeleton",
- "dying"
+ "dying",
+ "halloween",
+ "skull"
],
"moji": "💀"
},
"skull_crossbones": {
"unicode": "2620",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "skull and crossbones",
"shortname": ":skull_crossbones:",
"category": "objects",
@@ -24556,15 +27692,19 @@
"death",
"face",
"monster",
- "person"
- ]
+ "person",
+ "symbol",
+ "dead",
+ "skull"
+ ],
+ "moji": "☠"
},
"sleeping": {
"unicode": "1F634",
"unicode_alternates": [],
"name": "sleeping face",
"shortname": ":sleeping:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -24572,9 +27712,11 @@
"sleepy",
"tired",
"sleep",
- "sleepy",
"sleeping",
- "snore"
+ "snore",
+ "smiley",
+ "emotion",
+ "goodnight"
],
"moji": "😴"
},
@@ -24583,21 +27725,23 @@
"unicode_alternates": [],
"name": "sleeping accommodation",
"shortname": ":sleeping_accommodation:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"hotel",
"motel",
- "rest"
- ]
+ "rest",
+ "tired"
+ ],
+ "moji": "🛌"
},
"sleepy": {
"unicode": "1F62A",
"unicode_alternates": [],
"name": "sleepy face",
"shortname": ":sleepy:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -24605,8 +27749,10 @@
"rest",
"tired",
"sleepy",
- "tired",
- "exhausted"
+ "exhausted",
+ "smiley",
+ "sick",
+ "emotion"
],
"moji": "😪"
},
@@ -24624,8 +27770,12 @@
"slight",
"frown",
"unhappy",
- "disappointed"
- ]
+ "disappointed",
+ "sad",
+ "smiley",
+ "emotion"
+ ],
+ "moji": "🙁"
},
"slight_smile": {
"unicode": "1F642",
@@ -24640,15 +27790,17 @@
"keywords": [
"slight",
"smile",
- "happy"
- ]
+ "happy",
+ "smiley"
+ ],
+ "moji": "🙂"
},
"slot_machine": {
"unicode": "1F3B0",
"unicode_alternates": [],
"name": "slot machine",
"shortname": ":slot_machine:",
- "category": "places",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -24657,10 +27809,11 @@
"vegas",
"slot",
"machine",
- "gamble",
"one-armed bandit",
"slots",
- "luck"
+ "luck",
+ "game",
+ "boys night"
],
"moji": "🎰"
},
@@ -24669,11 +27822,13 @@
"unicode_alternates": [],
"name": "small blue diamond",
"shortname": ":small_blue_diamond:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "shape"
+ "shape",
+ "shapes",
+ "symbol"
],
"moji": "🔹"
},
@@ -24682,11 +27837,13 @@
"unicode_alternates": [],
"name": "small orange diamond",
"shortname": ":small_orange_diamond:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "shape"
+ "shape",
+ "shapes",
+ "symbol"
],
"moji": "🔸"
},
@@ -24695,11 +27852,14 @@
"unicode_alternates": [],
"name": "up-pointing red triangle",
"shortname": ":small_red_triangle:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "shape"
+ "shape",
+ "shapes",
+ "symbol",
+ "triangle"
],
"moji": "🔺"
},
@@ -24708,11 +27868,14 @@
"unicode_alternates": [],
"name": "down-pointing red triangle",
"shortname": ":small_red_triangle_down:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "shape"
+ "shape",
+ "shapes",
+ "symbol",
+ "triangle"
],
"moji": "🔻"
},
@@ -24721,7 +27884,7 @@
"unicode_alternates": [],
"name": "smiling face with open mouth and smiling eyes",
"shortname": ":smile:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [
":)",
@@ -24739,7 +27902,8 @@
"laugh",
"smile",
"smiley",
- "smiling"
+ "smiling",
+ "emotion"
],
"moji": "😄"
},
@@ -24748,7 +27912,7 @@
"unicode_alternates": [],
"name": "grinning cat face with smiling eyes",
"shortname": ":smile_cat:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -24757,7 +27921,8 @@
"cat",
"smile",
"grin",
- "grinning"
+ "grinning",
+ "happy"
],
"moji": "😸"
},
@@ -24766,7 +27931,7 @@
"unicode_alternates": [],
"name": "smiling face with open mouth",
"shortname": ":smiley:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [
":D",
@@ -24780,7 +27945,9 @@
"joy",
"smiling",
"smile",
- "smiley"
+ "smiley",
+ "emotion",
+ "good"
],
"moji": "😃"
},
@@ -24789,7 +27956,7 @@
"unicode_alternates": [],
"name": "smiling cat face with open mouth",
"shortname": ":smiley_cat:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -24798,8 +27965,7 @@
"happy",
"smile",
"smiley",
- "cat",
- "happy"
+ "cat"
],
"moji": "😺"
},
@@ -24808,16 +27974,19 @@
"unicode_alternates": [],
"name": "smiling face with horns",
"shortname": ":smiling_imp:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"devil",
"horns",
- "horns",
- "devil",
"impish",
- "trouble"
+ "trouble",
+ "silly",
+ "smiley",
+ "angry",
+ "monster",
+ "boys night"
],
"moji": "😈"
},
@@ -24826,7 +27995,7 @@
"unicode_alternates": [],
"name": "smirking face",
"shortname": ":smirk:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -24836,10 +28005,12 @@
"smug",
"smirking",
"smirk",
- "smug",
- "smile",
"half-smile",
- "conceited"
+ "conceited",
+ "silly",
+ "smiley",
+ "sexy",
+ "sarcastic"
],
"moji": "😏"
},
@@ -24848,7 +28019,7 @@
"unicode_alternates": [],
"name": "cat face with wry smile",
"shortname": ":smirk_cat:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -24858,7 +28029,8 @@
"smirking",
"wry",
"confident",
- "confidence"
+ "confidence",
+ "cat"
],
"moji": "😼"
},
@@ -24875,13 +28047,14 @@
"kills",
"tobacco",
"smoking",
- "cigarette",
"smoke",
"cancer",
"lungs",
"inhale",
"tar",
- "nicotine"
+ "nicotine",
+ "symbol",
+ "drugs"
],
"moji": "🚬"
},
@@ -24898,10 +28071,10 @@
"shell",
"slow",
"snail",
- "slow",
"escargot",
"french",
- "appetizer"
+ "appetizer",
+ "insects"
],
"moji": "🐌"
},
@@ -24915,16 +28088,32 @@
"aliases_ascii": [],
"keywords": [
"animal",
- "evil"
+ "evil",
+ "wildlife",
+ "reptile",
+ "creationism"
],
"moji": "🐍"
},
+ "sneezing_face": {
+ "unicode": "1F927",
+ "unicode_alternates": [],
+ "name": "sneezing face",
+ "shortname": ":sneezing_face:",
+ "category": "people",
+ "aliases": [
+ ":sneeze:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤧"
+ },
"snowboarder": {
"unicode": "1F3C2",
"unicode_alternates": [],
"name": "snowboarder",
"shortname": ":snowboarder:",
- "category": "objects",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -24932,13 +28121,16 @@
"winter",
"snow",
"boarding",
- "sports",
"freestyle",
"halfpipe",
"board",
"mountain",
"alpine",
- "winter"
+ "hat",
+ "vacation",
+ "cold",
+ "sport",
+ "snowboarding"
],
"moji": "🏂"
},
@@ -24965,13 +28157,13 @@
"droplet",
"ice",
"crystal",
- "cold",
"chilly",
- "winter",
"unique",
"special",
"below zero",
- "elsa"
+ "elsa",
+ "sky",
+ "holidays"
],
"moji": "❄"
},
@@ -24991,13 +28183,15 @@
"season",
"weather",
"winter",
- "xmas"
+ "xmas",
+ "holidays",
+ "snow"
],
"moji": "⛄"
},
"snowman2": {
"unicode": "2603",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "snowman",
"shortname": ":snowman2:",
"category": "nature",
@@ -25007,15 +28201,19 @@
"cold",
"nature",
"snow",
- "weather"
- ]
+ "weather",
+ "winter",
+ "holidays",
+ "christmas"
+ ],
+ "moji": "☃"
},
"sob": {
"unicode": "1F62D",
"unicode_alternates": [],
"name": "loudly crying face",
"shortname": ":sob:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -25024,14 +28222,14 @@
"sad",
"tears",
"upset",
- "cry",
"sob",
- "tears",
- "sad",
"melancholy",
"morn",
"somber",
- "hurt"
+ "hurt",
+ "smiley",
+ "emotion",
+ "heartbreak"
],
"moji": "😭"
},
@@ -25042,7 +28240,7 @@
],
"name": "soccer ball",
"shortname": ":soccer:",
- "category": "objects",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -25051,7 +28249,10 @@
"football",
"sports",
"european",
- "football"
+ "game",
+ "ball",
+ "sport",
+ "soccer"
],
"moji": "⚽"
},
@@ -25060,12 +28261,13 @@
"unicode_alternates": [],
"name": "soon with rightwards arrow above",
"shortname": ":soon:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"arrow",
- "words"
+ "words",
+ "symbol"
],
"moji": "🔜"
},
@@ -25074,14 +28276,15 @@
"unicode_alternates": [],
"name": "squared sos",
"shortname": ":sos:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"emergency",
"help",
"red-square",
- "words"
+ "words",
+ "symbol"
],
"moji": "🆘"
},
@@ -25090,12 +28293,14 @@
"unicode_alternates": [],
"name": "speaker with one sound wave",
"shortname": ":sound:",
- "category": "objects",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"speaker",
- "volume"
+ "volume",
+ "alarm",
+ "symbol"
],
"moji": "🔉"
},
@@ -25104,12 +28309,14 @@
"unicode_alternates": [],
"name": "alien monster",
"shortname": ":space_invader:",
- "category": "objects",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"arcade",
- "game"
+ "game",
+ "monster",
+ "alien"
],
"moji": "👾"
},
@@ -25120,12 +28327,14 @@
],
"name": "black spade suit",
"shortname": ":spades:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"cards",
- "poker"
+ "poker",
+ "symbol",
+ "game"
],
"moji": "♠"
},
@@ -25134,7 +28343,7 @@
"unicode_alternates": [],
"name": "spaghetti",
"shortname": ":spaghetti:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -25145,7 +28354,7 @@
"noodles",
"tomato",
"sauce",
- "italian"
+ "pasta"
],
"moji": "🍝"
},
@@ -25156,12 +28365,13 @@
],
"name": "sparkle",
"shortname": ":sparkle:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"green-square",
- "stars"
+ "stars",
+ "symbol"
],
"moji": "❇"
},
@@ -25170,13 +28380,14 @@
"unicode_alternates": [],
"name": "firework sparkler",
"shortname": ":sparkler:",
- "category": "objects",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"night",
"shine",
- "stars"
+ "stars",
+ "parties"
],
"moji": "🎇"
},
@@ -25185,14 +28396,16 @@
"unicode_alternates": [],
"name": "sparkles",
"shortname": ":sparkles:",
- "category": "emoticons",
+ "category": "nature",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"cool",
"shine",
"shiny",
- "stars"
+ "stars",
+ "star",
+ "girls night"
],
"moji": "✨"
},
@@ -25201,14 +28414,16 @@
"unicode_alternates": [],
"name": "sparkling heart",
"shortname": ":sparkling_heart:",
- "category": "emoticons",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"affection",
"like",
"love",
- "valentines"
+ "valentines",
+ "symbol",
+ "girls night"
],
"moji": "💖"
},
@@ -25217,13 +28432,12 @@
"unicode_alternates": [],
"name": "speak-no-evil monkey",
"shortname": ":speak_no_evil:",
- "category": "emoticons",
+ "category": "nature",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"animal",
"monkey",
- "monkey",
"mouth",
"talk",
"say",
@@ -25240,36 +28454,41 @@
"unicode_alternates": [],
"name": "speaker",
"shortname": ":speaker:",
- "category": "objects",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"sound",
"listen",
"hear",
- "noise"
- ]
+ "noise",
+ "alarm",
+ "symbol"
+ ],
+ "moji": "🔈"
},
"speaking_head": {
"unicode": "1F5E3",
"unicode_alternates": [],
"name": "speaking head in silhouette",
"shortname": ":speaking_head:",
- "category": "objects_symbols",
+ "category": "people",
"aliases": [
":speaking_head_in_silhouette:"
],
"aliases_ascii": [],
"keywords": [
- "talk"
- ]
+ "talk",
+ "people"
+ ],
+ "moji": "🗣"
},
"speech_balloon": {
"unicode": "1F4AC",
"unicode_alternates": [],
"name": "speech balloon",
"shortname": ":speech_balloon:",
- "category": "emoticons",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -25281,96 +28500,18 @@
"conversation",
"communication",
"comic",
- "dialogue"
+ "dialogue",
+ "symbol",
+ "free speech"
],
"moji": "💬"
},
- "speech_left": {
- "unicode": "1F5E8",
- "unicode_alternates": [],
- "name": "left speech bubble",
- "shortname": ":speech_left:",
- "category": "objects_symbols",
- "aliases": [
- ":left_speech_bubble:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "balloon",
- "words",
- "talk",
- "conversation",
- "communication",
- "comic",
- "dialogue"
- ]
- },
- "speech_right": {
- "unicode": "1F5E9",
- "unicode_alternates": [],
- "name": "right speech bubble",
- "shortname": ":speech_right:",
- "category": "objects_symbols",
- "aliases": [
- ":right_speech_bubble:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "balloon",
- "words",
- "talk",
- "conversation",
- "communication",
- "comic",
- "dialogue"
- ]
- },
- "speech_three": {
- "unicode": "1F5EB",
- "unicode_alternates": [],
- "name": "three speech bubbles",
- "shortname": ":speech_three:",
- "category": "objects_symbols",
- "aliases": [
- ":three_speech_bubbles:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "balloon",
- "words",
- "talk",
- "conversation",
- "communication",
- "comic",
- "dialogue"
- ]
- },
- "speech_two": {
- "unicode": "1F5EA",
- "unicode_alternates": [],
- "name": "two speech bubbles",
- "shortname": ":speech_two:",
- "category": "objects_symbols",
- "aliases": [
- ":two_speech_bubbles:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "balloon",
- "words",
- "talk",
- "conversation",
- "communication",
- "comic",
- "dialogue"
- ]
- },
"speedboat": {
"unicode": "1F6A4",
"unicode_alternates": [],
"name": "speedboat",
"shortname": ":speedboat:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -25381,7 +28522,10 @@
"speed",
"ski",
"power",
- "boat"
+ "boat",
+ "travel",
+ "vacation",
+ "tropical"
],
"moji": "🚤"
},
@@ -25395,8 +28539,12 @@
"aliases_ascii": [],
"keywords": [
"arachnid",
- "eight-legged"
- ]
+ "eight-legged",
+ "insects",
+ "halloween",
+ "animal"
+ ],
+ "moji": "🕷"
},
"spider_web": {
"unicode": "1F578",
@@ -25407,8 +28555,21 @@
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "cobweb"
- ]
+ "cobweb",
+ "halloween"
+ ],
+ "moji": "🕸"
+ },
+ "spoon": {
+ "unicode": "1F944",
+ "unicode_alternates": [],
+ "name": "spoon",
+ "shortname": ":spoon:",
+ "category": "food",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🥄"
},
"spy": {
"unicode": "1F575",
@@ -25423,12 +28584,19 @@
"keywords": [
"pi",
"undercover",
- "investigator"
- ]
+ "investigator",
+ "people",
+ "hat",
+ "men",
+ "glasses",
+ "diversity",
+ "job"
+ ],
+ "moji": "🕵"
},
"spy_tone1": {
"unicode": "1F575-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "sleuth or spy tone 1",
"shortname": ":spy_tone1:",
"category": "people",
@@ -25441,11 +28609,12 @@
"undercover",
"investigator",
"person"
- ]
+ ],
+ "moji": "🕵🏻"
},
"spy_tone2": {
"unicode": "1F575-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "sleuth or spy tone 2",
"shortname": ":spy_tone2:",
"category": "people",
@@ -25458,11 +28627,12 @@
"undercover",
"investigator",
"person"
- ]
+ ],
+ "moji": "🕵🏼"
},
"spy_tone3": {
"unicode": "1F575-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "sleuth or spy tone 3",
"shortname": ":spy_tone3:",
"category": "people",
@@ -25475,11 +28645,12 @@
"undercover",
"investigator",
"person"
- ]
+ ],
+ "moji": "🕵🏽"
},
"spy_tone4": {
"unicode": "1F575-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "sleuth or spy tone 4",
"shortname": ":spy_tone4:",
"category": "people",
@@ -25492,11 +28663,12 @@
"undercover",
"investigator",
"person"
- ]
+ ],
+ "moji": "🕵🏾"
},
"spy_tone5": {
"unicode": "1F575-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "sleuth or spy tone 5",
"shortname": ":spy_tone5:",
"category": "people",
@@ -25509,14 +28681,26 @@
"undercover",
"investigator",
"person"
- ]
+ ],
+ "moji": "🕵🏿"
+ },
+ "squid": {
+ "unicode": "1F991",
+ "unicode_alternates": [],
+ "name": "squid",
+ "shortname": ":squid:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🦑"
},
"stadium": {
"unicode": "1F3DF",
"unicode_alternates": [],
"name": "stadium",
"shortname": ":stadium:",
- "category": "travel_places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -25524,8 +28708,14 @@
"event",
"concert",
"convention",
- "game"
- ]
+ "game",
+ "places",
+ "building",
+ "travel",
+ "vacation",
+ "boys night"
+ ],
+ "moji": "🏟"
},
"star": {
"unicode": "2B50",
@@ -25539,7 +28729,10 @@
"aliases_ascii": [],
"keywords": [
"night",
- "yellow"
+ "yellow",
+ "space",
+ "sky",
+ "star"
],
"moji": "⭐"
},
@@ -25548,7 +28741,7 @@
"unicode_alternates": [],
"name": "glowing star",
"shortname": ":star2:",
- "category": "emoticons",
+ "category": "nature",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -25559,13 +28752,15 @@
"star",
"five",
"points",
- "classic"
+ "classic",
+ "space",
+ "sky"
],
"moji": "🌟"
},
"star_and_crescent": {
"unicode": "262A",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "star and crescent",
"shortname": ":star_and_crescent:",
"category": "symbols",
@@ -25576,11 +28771,12 @@
"muslim",
"religion",
"symbol"
- ]
+ ],
+ "moji": "☪"
},
"star_of_david": {
"unicode": "2721",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "star of david",
"shortname": ":star_of_david:",
"category": "symbols",
@@ -25590,15 +28786,17 @@
"jew",
"jewish",
"religion",
- "symbol"
- ]
+ "symbol",
+ "star"
+ ],
+ "moji": "✡"
},
"stars": {
"unicode": "1F320",
"unicode_alternates": [],
"name": "shooting star",
"shortname": ":stars:",
- "category": "nature",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -25608,9 +28806,9 @@
"shoot",
"star",
"sky",
- "night",
"comet",
- "meteoroid"
+ "meteoroid",
+ "space"
],
"moji": "🌠"
},
@@ -25619,7 +28817,7 @@
"unicode_alternates": [],
"name": "station",
"shortname": ":station:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -25628,7 +28826,8 @@
"vehicle",
"station",
"train",
- "subway"
+ "subway",
+ "travel"
],
"moji": "🚉"
},
@@ -25637,12 +28836,18 @@
"unicode_alternates": [],
"name": "statue of liberty",
"shortname": ":statue_of_liberty:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"american",
- "newyork"
+ "newyork",
+ "places",
+ "america",
+ "travel",
+ "vacation",
+ "statue of liberty",
+ "free speech"
],
"moji": "🗽"
},
@@ -25651,7 +28856,7 @@
"unicode_alternates": [],
"name": "steam locomotive",
"shortname": ":steam_locomotive:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -25660,35 +28865,17 @@
"vehicle",
"locomotive",
"steam",
- "train",
- "engine"
+ "engine",
+ "travel"
],
"moji": "🚂"
},
- "stereo": {
- "unicode": "1F4FE",
- "unicode_alternates": [],
- "name": "portable stereo",
- "shortname": ":stereo:",
- "category": "objects_symbols",
- "aliases": [
- ":portable_stereo:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "communication",
- "music",
- "program",
- "boom",
- "box"
- ]
- },
"stew": {
"unicode": "1F372",
"unicode_alternates": [],
"name": "pot of food",
"shortname": ":stew:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -25699,28 +28886,14 @@
"soup",
"thick",
"hot",
- "pot"
+ "pot",
+ "steam"
],
"moji": "🍲"
},
- "stock_chart": {
- "unicode": "1F5E0",
- "unicode_alternates": [],
- "name": "stock chart",
- "shortname": ":stock_chart:",
- "category": "objects_symbols",
- "aliases": [],
- "aliases_ascii": [],
- "keywords": [
- "graph",
- "presentation",
- "stats",
- "business"
- ]
- },
"stop_button": {
"unicode": "23F9",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "black square for stop",
"shortname": ":stop_button:",
"category": "symbols",
@@ -25728,12 +28901,14 @@
"aliases_ascii": [],
"keywords": [
"sound",
- "symbol"
- ]
+ "symbol",
+ "square"
+ ],
+ "moji": "⏹"
},
"stopwatch": {
"unicode": "23F1",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "stopwatch",
"shortname": ":stopwatch:",
"category": "objects",
@@ -25742,8 +28917,10 @@
"keywords": [
"clock",
"object",
- "time"
- ]
+ "time",
+ "electronics"
+ ],
+ "moji": "⏱"
},
"straight_ruler": {
"unicode": "1F4CF",
@@ -25754,7 +28931,10 @@
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "stationery"
+ "stationery",
+ "object",
+ "tool",
+ "office"
],
"moji": "📏"
},
@@ -25763,7 +28943,7 @@
"unicode_alternates": [],
"name": "strawberry",
"shortname": ":strawberry:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -25782,7 +28962,7 @@
"unicode_alternates": [],
"name": "face with stuck-out tongue",
"shortname": ":stuck_out_tongue:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [
":P",
@@ -25807,8 +28987,10 @@
"prank",
"tongue",
"silly",
- "playful",
- "cheeky"
+ "cheeky",
+ "smiley",
+ "sex",
+ "emotion"
],
"moji": "😛"
},
@@ -25817,7 +28999,7 @@
"unicode_alternates": [],
"name": "face with stuck-out tongue and tightly-closed eyes",
"shortname": ":stuck_out_tongue_closed_eyes:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -25828,8 +29010,10 @@
"tongue",
"kidding",
"silly",
- "playful",
- "ecstatic"
+ "ecstatic",
+ "happy",
+ "smiley",
+ "emotion"
],
"moji": "😝"
},
@@ -25838,7 +29022,7 @@
"unicode_alternates": [],
"name": "face with stuck-out tongue and winking eye",
"shortname": ":stuck_out_tongue_winking_eye:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [
">:P",
@@ -25856,11 +29040,27 @@
"winking",
"kidding",
"silly",
- "playful",
- "crazy"
+ "crazy",
+ "happy",
+ "smiley",
+ "emotion",
+ "parties"
],
"moji": "😜"
},
+ "stuffed_flatbread": {
+ "unicode": "1F959",
+ "unicode_alternates": [],
+ "name": "stuffed flatbread",
+ "shortname": ":stuffed_flatbread:",
+ "category": "food",
+ "aliases": [
+ ":stuffed_pita:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🥙"
+ },
"sun_with_face": {
"unicode": "1F31E",
"unicode_alternates": [],
@@ -25874,7 +29074,9 @@
"sun",
"anthropomorphic",
"face",
- "sky"
+ "sky",
+ "day",
+ "hump day"
],
"moji": "🌞"
},
@@ -25902,7 +29104,7 @@
"unicode_alternates": [],
"name": "smiling face with sunglasses",
"shortname": ":sunglasses:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [
"B-)",
@@ -25920,8 +29122,11 @@
"sun",
"glasses",
"sunny",
- "cool",
- "smooth"
+ "smooth",
+ "silly",
+ "smiley",
+ "emojione",
+ "boys night"
],
"moji": "😎"
},
@@ -25937,15 +29142,21 @@
"aliases_ascii": [],
"keywords": [
"brightness",
- "weather"
- ]
+ "weather",
+ "sky",
+ "day",
+ "sun",
+ "hot",
+ "morning"
+ ],
+ "moji": "☀"
},
"sunrise": {
"unicode": "1F305",
"unicode_alternates": [],
"name": "sunrise",
"shortname": ":sunrise:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -25955,9 +29166,13 @@
"view",
"sunrise",
"sun",
- "morning",
"color",
- "sky"
+ "sky",
+ "places",
+ "travel",
+ "tropical",
+ "day",
+ "hump day"
],
"moji": "🌅"
},
@@ -25966,7 +29181,7 @@
"unicode_alternates": [],
"name": "sunrise over mountains",
"shortname": ":sunrise_over_mountains:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -25979,7 +29194,11 @@
"mountain",
"rural",
"color",
- "sky"
+ "sky",
+ "places",
+ "travel",
+ "day",
+ "camp"
],
"moji": "🌄"
},
@@ -25988,7 +29207,7 @@
"unicode_alternates": [],
"name": "surfer",
"shortname": ":surfer:",
- "category": "objects",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -25998,15 +29217,19 @@
"surfer",
"surf",
"wave",
- "ocean",
"ride",
- "swell"
+ "swell",
+ "men",
+ "vacation",
+ "tropical",
+ "sport",
+ "diversity"
],
"moji": "🏄"
},
"surfer_tone1": {
"unicode": "1F3C4-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "surfer tone 1",
"shortname": ":surfer_tone1:",
"category": "activity",
@@ -26018,14 +29241,14 @@
"sport",
"surf",
"wave",
- "ocean",
"ride",
"swell"
- ]
+ ],
+ "moji": "🏄🏻"
},
"surfer_tone2": {
"unicode": "1F3C4-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "surfer tone 2",
"shortname": ":surfer_tone2:",
"category": "activity",
@@ -26037,14 +29260,14 @@
"sport",
"surf",
"wave",
- "ocean",
"ride",
"swell"
- ]
+ ],
+ "moji": "🏄🏼"
},
"surfer_tone3": {
"unicode": "1F3C4-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "surfer tone 3",
"shortname": ":surfer_tone3:",
"category": "activity",
@@ -26056,14 +29279,14 @@
"sport",
"surf",
"wave",
- "ocean",
"ride",
"swell"
- ]
+ ],
+ "moji": "🏄🏽"
},
"surfer_tone4": {
"unicode": "1F3C4-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "surfer tone 4",
"shortname": ":surfer_tone4:",
"category": "activity",
@@ -26075,14 +29298,14 @@
"sport",
"surf",
"wave",
- "ocean",
"ride",
"swell"
- ]
+ ],
+ "moji": "🏄🏾"
},
"surfer_tone5": {
"unicode": "1F3C4-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "surfer tone 5",
"shortname": ":surfer_tone5:",
"category": "activity",
@@ -26094,17 +29317,17 @@
"sport",
"surf",
"wave",
- "ocean",
"ride",
"swell"
- ]
+ ],
+ "moji": "🏄🏿"
},
"sushi": {
"unicode": "1F363",
"unicode_alternates": [],
"name": "sushi",
"shortname": ":sushi:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -26114,7 +29337,7 @@
"fish",
"raw",
"nigiri",
- "japanese"
+ "japan"
],
"moji": "🍣"
},
@@ -26123,7 +29346,7 @@
"unicode_alternates": [],
"name": "suspension railway",
"shortname": ":suspension_railway:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -26133,7 +29356,7 @@
"railway",
"rail",
"train",
- "transportation"
+ "travel"
],
"moji": "🚟"
},
@@ -26142,7 +29365,7 @@
"unicode_alternates": [],
"name": "face with cold sweat",
"shortname": ":sweat:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [
"':(",
@@ -26158,7 +29381,11 @@
"clammy",
"diaphoresis",
"face",
- "hot"
+ "hot",
+ "sad",
+ "smiley",
+ "stressed",
+ "emotion"
],
"moji": "😓"
},
@@ -26167,11 +29394,14 @@
"unicode_alternates": [],
"name": "splashing sweat symbol",
"shortname": ":sweat_drops:",
- "category": "emoticons",
+ "category": "nature",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "water"
+ "water",
+ "rain",
+ "stressed",
+ "sweat"
],
"moji": "💦"
},
@@ -26180,7 +29410,7 @@
"unicode_alternates": [],
"name": "smiling face with open mouth and cold sweat",
"shortname": ":sweat_smile:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [
"':)",
@@ -26197,7 +29427,10 @@
"smiling",
"cold",
"sweat",
- "perspiration"
+ "perspiration",
+ "smiley",
+ "workout",
+ "emotion"
],
"moji": "😅"
},
@@ -26206,7 +29439,7 @@
"unicode_alternates": [],
"name": "roasted sweet potato",
"shortname": ":sweet_potato:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -26216,7 +29449,8 @@
"potato",
"potassium",
"roasted",
- "roast"
+ "roast",
+ "vegetables"
],
"moji": "🍠"
},
@@ -26225,7 +29459,7 @@
"unicode_alternates": [],
"name": "swimmer",
"shortname": ":swimmer:",
- "category": "objects",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -26238,13 +29472,16 @@
"freestyle",
"butterfly",
"breaststroke",
- "backstroke"
+ "backstroke",
+ "workout",
+ "sport",
+ "diversity"
],
"moji": "🏊"
},
"swimmer_tone1": {
"unicode": "1F3CA-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "swimmer tone 1",
"shortname": ":swimmer_tone1:",
"category": "activity",
@@ -26260,11 +29497,12 @@
"butterfly",
"breaststroke",
"backstroke"
- ]
+ ],
+ "moji": "🏊🏻"
},
"swimmer_tone2": {
"unicode": "1F3CA-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "swimmer tone 2",
"shortname": ":swimmer_tone2:",
"category": "activity",
@@ -26280,11 +29518,12 @@
"butterfly",
"breaststroke",
"backstroke"
- ]
+ ],
+ "moji": "🏊🏼"
},
"swimmer_tone3": {
"unicode": "1F3CA-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "swimmer tone 3",
"shortname": ":swimmer_tone3:",
"category": "activity",
@@ -26300,11 +29539,12 @@
"butterfly",
"breaststroke",
"backstroke"
- ]
+ ],
+ "moji": "🏊🏽"
},
"swimmer_tone4": {
"unicode": "1F3CA-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "swimmer tone 4",
"shortname": ":swimmer_tone4:",
"category": "activity",
@@ -26320,11 +29560,12 @@
"butterfly",
"breaststroke",
"backstroke"
- ]
+ ],
+ "moji": "🏊🏾"
},
"swimmer_tone5": {
"unicode": "1F3CA-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "swimmer tone 5",
"shortname": ":swimmer_tone5:",
"category": "activity",
@@ -26340,30 +29581,40 @@
"butterfly",
"breaststroke",
"backstroke"
- ]
+ ],
+ "moji": "🏊🏿"
},
"symbols": {
"unicode": "1F523",
"unicode_alternates": [],
"name": "input symbol for symbols",
"shortname": ":symbols:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "blue-square"
+ "blue-square",
+ "symbol"
],
"moji": "🔣"
},
"synagogue": {
"unicode": "1F54D",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "synagogue",
"shortname": ":synagogue:",
"category": "travel",
"aliases": [],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "places",
+ "religion",
+ "building",
+ "travel",
+ "vacation",
+ "condolence"
+ ],
+ "moji": "🕍"
},
"syringe": {
"unicode": "1F489",
@@ -26379,19 +29630,26 @@
"health",
"hospital",
"medicine",
- "needle"
+ "needle",
+ "object",
+ "weapon"
],
"moji": "💉"
},
"taco": {
"unicode": "1F32E",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "taco",
"shortname": ":taco:",
- "category": "foods",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "food",
+ "mexican",
+ "vagina"
+ ],
+ "moji": "🌮"
},
"tada": {
"unicode": "1F389",
@@ -26404,14 +29662,21 @@
"keywords": [
"contulations",
"party",
- "party",
"popper",
"tada",
"celebration",
"victory",
"announcement",
"climax",
- "congratulations"
+ "congratulations",
+ "object",
+ "birthday",
+ "holidays",
+ "cheers",
+ "good",
+ "girls night",
+ "boys night",
+ "parties"
],
"moji": "🎉"
},
@@ -26420,7 +29685,7 @@
"unicode_alternates": [],
"name": "tanabata tree",
"shortname": ":tanabata_tree:",
- "category": "objects",
+ "category": "nature",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -26431,7 +29696,8 @@
"festival",
"star",
"wish",
- "holiday"
+ "holiday",
+ "trees"
],
"moji": "🎋"
},
@@ -26440,7 +29706,7 @@
"unicode_alternates": [],
"name": "tangerine",
"shortname": ":tangerine:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -26460,7 +29726,7 @@
],
"name": "taurus",
"shortname": ":taurus:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -26473,9 +29739,8 @@
"constellation",
"stars",
"zodiac",
- "sign",
- "zodiac",
- "horoscope"
+ "horoscope",
+ "symbol"
],
"moji": "♉"
},
@@ -26484,7 +29749,7 @@
"unicode_alternates": [],
"name": "taxi",
"shortname": ":taxi:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -26497,7 +29762,8 @@
"automobile",
"city",
"transport",
- "service"
+ "service",
+ "travel"
],
"moji": "🚕"
},
@@ -26506,7 +29772,7 @@
"unicode_alternates": [],
"name": "teacup without handle",
"shortname": ":tea:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -26517,10 +29783,13 @@
"green",
"tea",
"leaf",
- "drink",
"teacup",
"hot",
- "beverage"
+ "beverage",
+ "japan",
+ "caffeine",
+ "steam",
+ "morning"
],
"moji": "🍵"
},
@@ -26537,26 +29806,12 @@
"keywords": [
"communication",
"dial",
- "technology"
+ "technology",
+ "electronics",
+ "phone"
],
"moji": "☎"
},
- "telephone_black": {
- "unicode": "1F57F",
- "unicode_alternates": [],
- "name": "black touchtone telephone",
- "shortname": ":telephone_black:",
- "category": "objects_symbols",
- "aliases": [
- ":black_touchtone_telephone:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "communication",
- "dial",
- "technology"
- ]
- },
"telephone_receiver": {
"unicode": "1F4DE",
"unicode_alternates": [],
@@ -26568,26 +29823,12 @@
"keywords": [
"communication",
"dial",
- "technology"
+ "technology",
+ "electronics",
+ "phone"
],
"moji": "📞"
},
- "telephone_white": {
- "unicode": "1F57E",
- "unicode_alternates": [],
- "name": "white touchtone telephone",
- "shortname": ":telephone_white:",
- "category": "objects_symbols",
- "aliases": [
- ":white_touchtone_telephone:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "communication",
- "dial",
- "technology"
- ]
- },
"telescope": {
"unicode": "1F52D",
"unicode_alternates": [],
@@ -26598,13 +29839,15 @@
"aliases_ascii": [],
"keywords": [
"space",
- "stars"
+ "stars",
+ "object",
+ "science"
],
"moji": "🔭"
},
"ten": {
"unicode": "1F51F",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "keycap ten",
"shortname": ":ten:",
"category": "symbols",
@@ -26615,15 +29858,18 @@
"blue-square",
"numbers",
"symbol",
- "word"
- ]
+ "word",
+ "number",
+ "math"
+ ],
+ "moji": "🔟"
},
"tennis": {
"unicode": "1F3BE",
"unicode_alternates": [],
"name": "tennis racquet and ball",
"shortname": ":tennis:",
- "category": "objects",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -26637,7 +29883,8 @@
"game",
"net",
"court",
- "love"
+ "love",
+ "sport"
],
"moji": "🎾"
},
@@ -26648,13 +29895,16 @@
],
"name": "tent",
"shortname": ":tent:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"camp",
"outdoors",
- "photo"
+ "photo",
+ "places",
+ "travel",
+ "vacation"
],
"moji": "⛺"
},
@@ -26663,16 +29913,21 @@
"unicode_alternates": [],
"name": "thermometer",
"shortname": ":thermometer:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "temperature"
- ]
+ "temperature",
+ "object",
+ "science",
+ "health",
+ "hot"
+ ],
+ "moji": "🌡"
},
"thermometer_face": {
"unicode": "1F912",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "face with thermometer",
"shortname": ":thermometer_face:",
"category": "people",
@@ -26680,11 +29935,17 @@
":face_with_thermometer:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "smiley",
+ "health",
+ "sick",
+ "emotion"
+ ],
+ "moji": "🤒"
},
"thinking": {
"unicode": "1F914",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "thinking face",
"shortname": ":thinking:",
"category": "people",
@@ -26692,14 +29953,32 @@
":thinking_face:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "smiley",
+ "thinking",
+ "boys night"
+ ],
+ "moji": "🤔"
+ },
+ "third_place": {
+ "unicode": "1F949",
+ "unicode_alternates": [],
+ "name": "third place medal",
+ "shortname": ":third_place:",
+ "category": "activity",
+ "aliases": [
+ ":third_place_medal:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🥉"
},
"thought_balloon": {
"unicode": "1F4AD",
"unicode_alternates": [],
"name": "thought balloon",
"shortname": ":thought_balloon:",
- "category": "emoticons",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -26711,98 +29990,30 @@
"comic",
"think",
"day dream",
- "wonder"
+ "wonder",
+ "symbol"
],
"moji": "💭"
},
- "thought_left": {
- "unicode": "1F5EC",
- "unicode_alternates": [],
- "name": "left thought bubble",
- "shortname": ":thought_left:",
- "category": "objects_symbols",
- "aliases": [
- ":left_thought_bubble:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "balloon",
- "cloud",
- "comic",
- "think",
- "day dream",
- "wonder"
- ]
- },
- "thought_right": {
- "unicode": "1F5ED",
- "unicode_alternates": [],
- "name": "right thought bubble",
- "shortname": ":thought_right:",
- "category": "objects_symbols",
- "aliases": [
- ":right_thought_bubble:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "balloon",
- "cloud",
- "comic",
- "think",
- "day dream",
- "wonder"
- ]
- },
"three": {
"moji": "3️⃣",
"unicode": "0033-20E3",
"unicode_alternates": [
"0033-FE0F-20E3"
],
- "name": "digit three",
+ "name": "keycap digit three",
"shortname": ":three:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"3",
"blue-square",
"numbers",
- "prime"
- ]
- },
- "thumbs_down_reverse": {
- "unicode": "1F593",
- "unicode_alternates": [],
- "name": "reversed thumbs down sign",
- "shortname": ":thumbs_down_reverse:",
- "category": "people",
- "aliases": [
- ":reversed_thumbs_down_sign:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "hand",
- "no",
- "-1"
- ]
- },
- "thumbs_up_reverse": {
- "unicode": "1F592",
- "unicode_alternates": [],
- "name": "reversed thumbs up sign",
- "shortname": ":thumbs_up_reverse:",
- "category": "people",
- "aliases": [
- ":reversed_thumbs_up_sign:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "cool",
- "hand",
- "like",
- "yes",
- "+1"
+ "prime",
+ "number",
+ "math",
+ "symbol"
]
},
"thumbsdown": {
@@ -26810,20 +30021,23 @@
"unicode_alternates": [],
"name": "thumbs down sign",
"shortname": ":thumbsdown:",
- "category": "emoticons",
+ "category": "people",
"aliases": [
":-1:"
],
"aliases_ascii": [],
"keywords": [
"hand",
- "no"
+ "no",
+ "body",
+ "hands",
+ "diversity"
],
"moji": "👎"
},
"thumbsdown_tone1": {
"unicode": "1F44E-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "thumbs down sign tone 1",
"shortname": ":thumbsdown_tone1:",
"category": "people",
@@ -26835,11 +30049,12 @@
"hand",
"no",
"-1"
- ]
+ ],
+ "moji": "👎🏻"
},
"thumbsdown_tone2": {
"unicode": "1F44E-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "thumbs down sign tone 2",
"shortname": ":thumbsdown_tone2:",
"category": "people",
@@ -26851,11 +30066,12 @@
"hand",
"no",
"-1"
- ]
+ ],
+ "moji": "👎🏼"
},
"thumbsdown_tone3": {
"unicode": "1F44E-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "thumbs down sign tone 3",
"shortname": ":thumbsdown_tone3:",
"category": "people",
@@ -26867,11 +30083,12 @@
"hand",
"no",
"-1"
- ]
+ ],
+ "moji": "👎🏽"
},
"thumbsdown_tone4": {
"unicode": "1F44E-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "thumbs down sign tone 4",
"shortname": ":thumbsdown_tone4:",
"category": "people",
@@ -26883,11 +30100,12 @@
"hand",
"no",
"-1"
- ]
+ ],
+ "moji": "👎🏾"
},
"thumbsdown_tone5": {
"unicode": "1F44E-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "thumbs down sign tone 5",
"shortname": ":thumbsdown_tone5:",
"category": "people",
@@ -26899,14 +30117,15 @@
"hand",
"no",
"-1"
- ]
+ ],
+ "moji": "👎🏿"
},
"thumbsup": {
"unicode": "1F44D",
"unicode_alternates": [],
"name": "thumbs up sign",
"shortname": ":thumbsup:",
- "category": "emoticons",
+ "category": "people",
"aliases": [
":+1:"
],
@@ -26915,13 +30134,22 @@
"cool",
"hand",
"like",
- "yes"
+ "yes",
+ "body",
+ "hands",
+ "hi",
+ "luck",
+ "thank you",
+ "diversity",
+ "perfect",
+ "good",
+ "beautiful"
],
"moji": "👍"
},
"thumbsup_tone1": {
"unicode": "1F44D-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "thumbs up sign tone 1",
"shortname": ":thumbsup_tone1:",
"category": "people",
@@ -26935,11 +30163,12 @@
"like",
"yes",
"+1"
- ]
+ ],
+ "moji": "👍🏻"
},
"thumbsup_tone2": {
"unicode": "1F44D-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "thumbs up sign tone 2",
"shortname": ":thumbsup_tone2:",
"category": "people",
@@ -26953,11 +30182,12 @@
"like",
"yes",
"+1"
- ]
+ ],
+ "moji": "👍🏼"
},
"thumbsup_tone3": {
"unicode": "1F44D-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "thumbs up sign tone 3",
"shortname": ":thumbsup_tone3:",
"category": "people",
@@ -26971,11 +30201,12 @@
"like",
"yes",
"+1"
- ]
+ ],
+ "moji": "👍🏽"
},
"thumbsup_tone4": {
"unicode": "1F44D-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "thumbs up sign tone 4",
"shortname": ":thumbsup_tone4:",
"category": "people",
@@ -26989,11 +30220,12 @@
"like",
"yes",
"+1"
- ]
+ ],
+ "moji": "👍🏾"
},
"thumbsup_tone5": {
"unicode": "1F44D-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "thumbs up sign tone 5",
"shortname": ":thumbsup_tone5:",
"category": "people",
@@ -27007,11 +30239,12 @@
"like",
"yes",
"+1"
- ]
+ ],
+ "moji": "👍🏿"
},
"thunder_cloud_rain": {
"unicode": "26C8",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "thunder cloud and rain",
"shortname": ":thunder_cloud_rain:",
"category": "nature",
@@ -27021,15 +30254,20 @@
"aliases_ascii": [],
"keywords": [
"nature",
- "weather"
- ]
+ "weather",
+ "sky",
+ "cloud",
+ "cold",
+ "rain"
+ ],
+ "moji": "⛈"
},
"ticket": {
"unicode": "1F3AB",
"unicode_alternates": [],
"name": "ticket",
"shortname": ":ticket:",
- "category": "places",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -27042,7 +30280,10 @@
"stub",
"admission",
"proof",
- "purchase"
+ "purchase",
+ "theatre",
+ "movie",
+ "parties"
],
"moji": "🎫"
},
@@ -27064,8 +30305,12 @@
"entertainment",
"stub",
"proof",
- "purchase"
- ]
+ "purchase",
+ "theatre",
+ "movie",
+ "parties"
+ ],
+ "moji": "🎟"
},
"tiger": {
"unicode": "1F42F",
@@ -27076,7 +30321,10 @@
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "animal"
+ "animal",
+ "wildlife",
+ "roar",
+ "cat"
],
"moji": "🐯"
},
@@ -27096,13 +30344,15 @@
"striped",
"tony",
"tigger",
- "hobs"
+ "hobs",
+ "wildlife",
+ "roar"
],
"moji": "🐅"
},
"timer": {
"unicode": "23F2",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "timer clock",
"shortname": ":timer:",
"category": "objects",
@@ -27113,14 +30363,15 @@
"keywords": [
"object",
"time"
- ]
+ ],
+ "moji": "⏲"
},
"tired_face": {
"unicode": "1F62B",
"unicode_alternates": [],
"name": "tired face",
"shortname": ":tired_face:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -27131,13 +30382,18 @@
"whine",
"exhausted",
"sleepy",
- "tired"
+ "tired",
+ "sad",
+ "smiley",
+ "emotion"
],
"moji": "😫"
},
"tm": {
"unicode": "2122",
- "unicode_alternates": "2122-fe0f",
+ "unicode_alternates": [
+ "2122-FE0F"
+ ],
"name": "trade mark sign",
"shortname": ":tm:",
"category": "symbols",
@@ -27149,7 +30405,8 @@
"symbol",
"tm",
"word"
- ]
+ ],
+ "moji": "™"
},
"toilet": {
"unicode": "1F6BD",
@@ -27168,7 +30425,8 @@
"porcelain",
"waste",
"flush",
- "plumbing"
+ "plumbing",
+ "object"
],
"moji": "🚽"
},
@@ -27177,12 +30435,16 @@
"unicode_alternates": [],
"name": "tokyo tower",
"shortname": ":tokyo_tower:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"japan",
- "photo"
+ "photo",
+ "places",
+ "travel",
+ "vacation",
+ "eiffel tower"
],
"moji": "🗼"
},
@@ -27191,7 +30453,7 @@
"unicode_alternates": [],
"name": "tomato",
"shortname": ":tomato:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -27200,80 +30462,83 @@
"nature",
"vegetable",
"tomato",
- "fruit",
"sauce",
- "italian"
+ "italian",
+ "vegetables"
],
"moji": "🍅"
},
"tone1": {
"unicode": "1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "emoji modifier Fitzpatrick type-1-2",
"shortname": ":tone1:",
"category": "modifier",
"aliases": [],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [],
+ "moji": "🏻"
},
"tone2": {
"unicode": "1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "emoji modifier Fitzpatrick type-3",
"shortname": ":tone2:",
"category": "modifier",
"aliases": [],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [],
+ "moji": "🏼"
},
"tone3": {
"unicode": "1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "emoji modifier Fitzpatrick type-4",
"shortname": ":tone3:",
"category": "modifier",
"aliases": [],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [],
+ "moji": "🏽"
},
"tone4": {
"unicode": "1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "emoji modifier Fitzpatrick type-5",
"shortname": ":tone4:",
"category": "modifier",
"aliases": [],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [],
+ "moji": "🏾"
},
"tone5": {
"unicode": "1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "emoji modifier Fitzpatrick type-6",
"shortname": ":tone5:",
"category": "modifier",
"aliases": [],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [],
+ "moji": "🏿"
},
"tongue": {
"unicode": "1F445",
"unicode_alternates": [],
"name": "tongue",
"shortname": ":tongue:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"mouth",
"playful",
"tongue",
- "mouth",
"taste",
"buds",
"food",
"silly",
- "playful",
"tease",
"kiss",
"french kiss",
@@ -27281,7 +30546,10 @@
"tasty",
"playfulness",
"silliness",
- "intimacy"
+ "intimacy",
+ "body",
+ "sexy",
+ "lip"
],
"moji": "👅"
},
@@ -27290,26 +30558,31 @@
"unicode_alternates": [],
"name": "hammer and wrench",
"shortname": ":tools:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [
":hammer_and_wrench:"
],
"aliases_ascii": [],
"keywords": [
- "tools"
- ]
+ "tools",
+ "object",
+ "tool"
+ ],
+ "moji": "🛠"
},
"top": {
"unicode": "1F51D",
"unicode_alternates": [],
"name": "top with upwards arrow above",
"shortname": ":top:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"blue-square",
- "words"
+ "words",
+ "arrow",
+ "symbol"
],
"moji": "🔝"
},
@@ -27318,7 +30591,7 @@
"unicode_alternates": [],
"name": "top hat",
"shortname": ":tophat:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -27337,14 +30610,15 @@
"topper",
"london",
"period piece",
- "magic",
- "magician"
+ "magician",
+ "fashion",
+ "accessories"
],
"moji": "🎩"
},
"track_next": {
"unicode": "23ED",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "black right-pointing double triangle with vertical bar",
"shortname": ":track_next:",
"category": "symbols",
@@ -27358,11 +30632,12 @@
"next track",
"sound",
"symbol"
- ]
+ ],
+ "moji": "⏭"
},
"track_previous": {
"unicode": "23EE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "black left-pointing double triangle with vertical bar",
"shortname": ":track_previous:",
"category": "symbols",
@@ -27376,28 +30651,34 @@
"previous track",
"sound",
"symbol"
- ]
+ ],
+ "moji": "⏮"
},
"trackball": {
"unicode": "1F5B2",
"unicode_alternates": [],
"name": "trackball",
"shortname": ":trackball:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"input",
"device",
- "gadget"
- ]
+ "gadget",
+ "electronics",
+ "work",
+ "game",
+ "office"
+ ],
+ "moji": "🖲"
},
"tractor": {
"unicode": "1F69C",
"unicode_alternates": [],
"name": "tractor",
"shortname": ":tractor:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -27409,7 +30690,8 @@
"farm",
"construction",
"machine",
- "digger"
+ "digger",
+ "transportation"
],
"moji": "🚜"
},
@@ -27418,18 +30700,19 @@
"unicode_alternates": [],
"name": "horizontal traffic light",
"shortname": ":traffic_light:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"traffic",
"transportation",
- "traffic",
"light",
"stop",
"go",
"yield",
- "horizontal"
+ "horizontal",
+ "object",
+ "stop light"
],
"moji": "🚥"
},
@@ -27438,20 +30721,24 @@
"unicode_alternates": [],
"name": "Tram Car",
"shortname": ":train:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"tram",
- "rail"
- ]
+ "rail",
+ "transportation",
+ "travel",
+ "train"
+ ],
+ "moji": "🚋"
},
"train2": {
"unicode": "1F686",
"unicode_alternates": [],
"name": "train",
"shortname": ":train2:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -27459,66 +30746,35 @@
"vehicle",
"train",
"locomotive",
- "rail"
+ "rail",
+ "travel"
],
"moji": "🚆"
},
- "train_diesel": {
- "unicode": "1F6F2",
- "unicode_alternates": [],
- "name": "diesel locomotive",
- "shortname": ":train_diesel:",
- "category": "travel_places",
- "aliases": [
- ":diesel_locomotive:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "train",
- "transportation",
- "engine",
- "rail"
- ]
- },
"tram": {
"unicode": "1F68A",
"unicode_alternates": [],
"name": "tram",
"shortname": ":tram:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"transportation",
"vehicle",
"tram",
- "transportation",
- "transport"
+ "transport",
+ "travel",
+ "train"
],
"moji": "🚊"
},
- "triangle_round": {
- "unicode": "1F6C6",
- "unicode_alternates": [],
- "name": "triangle with rounded corners",
- "shortname": ":triangle_round:",
- "category": "objects_symbols",
- "aliases": [
- ":triangle_with_rounded_corners:"
- ],
- "aliases_ascii": [],
- "keywords": [
- "caution",
- "warning",
- "alert"
- ]
- },
"triangular_flag_on_post": {
"unicode": "1F6A9",
"unicode_alternates": [],
"name": "triangular flag on post",
"shortname": ":triangular_flag_on_post:",
- "category": "places",
+ "category": "objects",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -27527,7 +30783,8 @@
"flag",
"golf",
"post",
- "flagpole"
+ "flagpole",
+ "object"
],
"moji": "🚩"
},
@@ -27543,7 +30800,10 @@
"architect",
"math",
"sketch",
- "stationery"
+ "stationery",
+ "object",
+ "tool",
+ "office"
],
"moji": "📐"
},
@@ -27552,12 +30812,14 @@
"unicode_alternates": [],
"name": "trident emblem",
"shortname": ":trident:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"spear",
- "weapon"
+ "weapon",
+ "object",
+ "symbol"
],
"moji": "🔱"
},
@@ -27566,7 +30828,7 @@
"unicode_alternates": [],
"name": "face with look of triumph",
"shortname": ":triumph:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -27575,7 +30837,11 @@
"phew",
"triumph",
"steam",
- "breath"
+ "breath",
+ "mad",
+ "smiley",
+ "angry",
+ "emotion"
],
"moji": "😤"
},
@@ -27584,7 +30850,7 @@
"unicode_alternates": [],
"name": "trolleybus",
"shortname": ":trolleybus:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -27595,7 +30861,7 @@
"bus",
"city",
"transport",
- "transportation"
+ "travel"
],
"moji": "🚎"
},
@@ -27604,7 +30870,7 @@
"unicode_alternates": [],
"name": "trophy",
"shortname": ":trophy:",
- "category": "objects",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -27617,11 +30883,13 @@
"trophy",
"first",
"show",
- "place",
- "win",
"reward",
"achievement",
- "medal"
+ "medal",
+ "object",
+ "game",
+ "perfect",
+ "parties"
],
"moji": "🏆"
},
@@ -27630,7 +30898,7 @@
"unicode_alternates": [],
"name": "tropical drink",
"shortname": ":tropical_drink:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -27642,7 +30910,9 @@
"coconut",
"pina",
"fruit",
- "umbrella"
+ "umbrella",
+ "cocktail",
+ "alcohol"
],
"moji": "🍹"
},
@@ -27656,7 +30926,8 @@
"aliases_ascii": [],
"keywords": [
"animal",
- "swim"
+ "swim",
+ "wildlife"
],
"moji": "🐠"
},
@@ -27665,7 +30936,7 @@
"unicode_alternates": [],
"name": "delivery truck",
"shortname": ":truck:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -27682,16 +30953,15 @@
"unicode_alternates": [],
"name": "trumpet",
"shortname": ":trumpet:",
- "category": "objects",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"brass",
"music",
"trumpet",
- "brass",
- "music",
- "instrument"
+ "instrument",
+ "instruments"
],
"moji": "🎺"
},
@@ -27711,34 +30981,40 @@
"flower",
"bulb",
"spring",
- "easter"
+ "easter",
+ "vagina",
+ "girls night"
],
"moji": "🌷"
},
+ "tumbler_glass": {
+ "unicode": "1F943",
+ "unicode_alternates": [],
+ "name": "tumbler glass",
+ "shortname": ":tumbler_glass:",
+ "category": "food",
+ "aliases": [
+ ":whisky:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [
+ "booze"
+ ],
+ "moji": "🥃"
+ },
"turkey": {
"unicode": "1F983",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "turkey",
"shortname": ":turkey:",
"category": "nature",
"aliases": [],
"aliases_ascii": [],
- "keywords": []
- },
- "turned_ok_hand": {
- "unicode": "1F58F",
- "unicode_alternates": [],
- "name": "turned ok hand sign",
- "shortname": ":turned_ok_hand:",
- "category": "people",
- "aliases": [
- ":turned_ok_hand_sign:"
- ],
- "aliases_ascii": [],
"keywords": [
- "perfect",
- "okay"
- ]
+ "wildlife",
+ "animal"
+ ],
+ "moji": "🦃"
},
"turtle": {
"unicode": "1F422",
@@ -27756,15 +31032,15 @@
"tortoise",
"chelonian",
"reptile",
- "slow",
"snap",
- "steady"
+ "steady",
+ "wildlife"
],
"moji": "🐢"
},
"tv": {
"unicode": "1F4FA",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "television",
"shortname": ":tv:",
"category": "objects",
@@ -27778,19 +31054,23 @@
"tv",
"entertainment",
"object",
- "video"
- ]
+ "video",
+ "electronics"
+ ],
+ "moji": "📺"
},
"twisted_rightwards_arrows": {
"unicode": "1F500",
"unicode_alternates": [],
"name": "twisted rightwards arrows",
"shortname": ":twisted_rightwards_arrows:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "blue-square"
+ "blue-square",
+ "arrow",
+ "symbol"
],
"moji": "🔀"
},
@@ -27800,16 +31080,19 @@
"unicode_alternates": [
"0032-FE0F-20E3"
],
- "name": "digit two",
+ "name": "keycap digit two",
"shortname": ":two:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"2",
"blue-square",
"numbers",
- "prime"
+ "prime",
+ "number",
+ "math",
+ "symbol"
]
},
"two_hearts": {
@@ -27817,7 +31100,7 @@
"unicode_alternates": [],
"name": "two hearts",
"shortname": ":two_hearts:",
- "category": "emoticons",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -27828,8 +31111,8 @@
"heart",
"hearts",
"two",
- "love",
- "emotion"
+ "emotion",
+ "symbol"
],
"moji": "💕"
},
@@ -27838,7 +31121,7 @@
"unicode_alternates": [],
"name": "two men holding hands",
"shortname": ":two_men_holding_hands:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -27850,11 +31133,13 @@
"men",
"gay",
"homosexual",
- "friends",
"hands",
"holding",
"team",
- "unity"
+ "unity",
+ "people",
+ "sex",
+ "lgbt"
],
"moji": "👬"
},
@@ -27863,7 +31148,7 @@
"unicode_alternates": [],
"name": "two women holding hands",
"shortname": ":two_women_holding_hands:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -27875,14 +31160,17 @@
"women",
"hands",
"girlfriends",
- "friends",
"sisters",
"mother",
"daughter",
"gay",
"homosexual",
- "couple",
- "unity"
+ "unity",
+ "people",
+ "sex",
+ "lgbt",
+ "lesbian",
+ "girls night"
],
"moji": "👭"
},
@@ -27891,7 +31179,7 @@
"unicode_alternates": [],
"name": "squared cjk unified ideograph-5272",
"shortname": ":u5272:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -27899,7 +31187,8 @@
"cut",
"divide",
"kanji",
- "pink"
+ "pink",
+ "symbol"
],
"moji": "🈹"
},
@@ -27908,14 +31197,16 @@
"unicode_alternates": [],
"name": "squared cjk unified ideograph-5408",
"shortname": ":u5408:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"chinese",
"japanese",
"join",
- "kanji"
+ "kanji",
+ "japan",
+ "symbol"
],
"moji": "🈴"
},
@@ -27924,12 +31215,13 @@
"unicode_alternates": [],
"name": "squared cjk unified ideograph-55b6",
"shortname": ":u55b6:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"japanese",
- "opening hours"
+ "opening hours",
+ "symbol"
],
"moji": "🈺"
},
@@ -27940,14 +31232,15 @@
],
"name": "squared cjk unified ideograph-6307",
"shortname": ":u6307:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"chinese",
"green-square",
"kanji",
- "point"
+ "point",
+ "symbol"
],
"moji": "🈯"
},
@@ -27956,7 +31249,7 @@
"unicode_alternates": [],
"name": "squared cjk unified ideograph-6708",
"shortname": ":u6708:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -27964,7 +31257,8 @@
"japanese",
"kanji",
"moon",
- "orange-square"
+ "orange-square",
+ "symbol"
],
"moji": "🈷"
},
@@ -27973,14 +31267,15 @@
"unicode_alternates": [],
"name": "squared cjk unified ideograph-6709",
"shortname": ":u6709:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"chinese",
"have",
"kanji",
- "orange-square"
+ "orange-square",
+ "symbol"
],
"moji": "🈶"
},
@@ -27989,7 +31284,7 @@
"unicode_alternates": [],
"name": "squared cjk unified ideograph-6e80",
"shortname": ":u6e80:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -27997,7 +31292,9 @@
"full",
"japanese",
"kanji",
- "red-square"
+ "red-square",
+ "japan",
+ "symbol"
],
"moji": "🈵"
},
@@ -28008,7 +31305,7 @@
],
"name": "squared cjk unified ideograph-7121",
"shortname": ":u7121:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -28017,7 +31314,8 @@
"kanji",
"no",
"nothing",
- "orange-square"
+ "orange-square",
+ "symbol"
],
"moji": "🈚"
},
@@ -28026,13 +31324,14 @@
"unicode_alternates": [],
"name": "squared cjk unified ideograph-7533",
"shortname": ":u7533:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"chinese",
"japanese",
- "kanji"
+ "kanji",
+ "symbol"
],
"moji": "🈸"
},
@@ -28041,7 +31340,7 @@
"unicode_alternates": [],
"name": "squared cjk unified ideograph-7981",
"shortname": ":u7981:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -28050,7 +31349,9 @@
"japanese",
"kanji",
"limit",
- "restricted"
+ "restricted",
+ "japan",
+ "symbol"
],
"moji": "🈲"
},
@@ -28059,14 +31360,15 @@
"unicode_alternates": [],
"name": "squared cjk unified ideograph-7a7a",
"shortname": ":u7a7a:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"chinese",
"empty",
"japanese",
- "kanji"
+ "kanji",
+ "symbol"
],
"moji": "🈳"
},
@@ -28082,13 +31384,15 @@
"aliases_ascii": [],
"keywords": [
"rain",
- "weather"
+ "weather",
+ "sky",
+ "cold"
],
"moji": "☔"
},
"umbrella2": {
"unicode": "2602",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "umbrella",
"shortname": ":umbrella2:",
"category": "nature",
@@ -28098,15 +31402,19 @@
"clothing",
"nature",
"rain",
- "weather"
- ]
+ "weather",
+ "object",
+ "sky",
+ "cold"
+ ],
+ "moji": "☂"
},
"unamused": {
"unicode": "1F612",
"unicode_alternates": [],
"name": "unamused face",
"shortname": ":unamused:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -28120,7 +31428,12 @@
"depressed",
"unhappy",
"disapprove",
- "lame"
+ "lame",
+ "sad",
+ "mad",
+ "smiley",
+ "tired",
+ "emotion"
],
"moji": "😒"
},
@@ -28129,20 +31442,21 @@
"unicode_alternates": [],
"name": "no one under eighteen symbol",
"shortname": ":underage:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"18",
"drink",
"night",
- "pub"
+ "pub",
+ "symbol"
],
"moji": "🔞"
},
"unicorn": {
"unicode": "1F984",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "unicorn face",
"shortname": ":unicorn:",
"category": "nature",
@@ -28150,7 +31464,10 @@
":unicorn_face:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "animal"
+ ],
+ "moji": "🦄"
},
"unlock": {
"unicode": "1F513",
@@ -28162,7 +31479,9 @@
"aliases_ascii": [],
"keywords": [
"privacy",
- "security"
+ "security",
+ "object",
+ "lock"
],
"moji": "🔓"
},
@@ -28171,17 +31490,18 @@
"unicode_alternates": [],
"name": "squared up with exclamation mark",
"shortname": ":up:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "blue-square"
+ "blue-square",
+ "symbol"
],
"moji": "🆙"
},
"upside_down": {
"unicode": "1F643",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "upside-down face",
"shortname": ":upside_down:",
"category": "people",
@@ -28189,11 +31509,16 @@
":upside_down_face:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "silly",
+ "smiley",
+ "sarcastic"
+ ],
+ "moji": "🙃"
},
"urn": {
"unicode": "26B1",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "funeral urn",
"shortname": ":urn:",
"category": "objects",
@@ -28203,8 +31528,11 @@
"aliases_ascii": [],
"keywords": [
"death",
- "object"
- ]
+ "object",
+ "dead",
+ "rip"
+ ],
+ "moji": "⚱"
},
"v": {
"unicode": "270C",
@@ -28213,7 +31541,7 @@
],
"name": "victory hand",
"shortname": ":v:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -28222,13 +31550,19 @@
"ohyeah",
"peace",
"two",
- "victory"
+ "victory",
+ "body",
+ "hands",
+ "hi",
+ "thank you",
+ "diversity",
+ "girls night"
],
"moji": "✌"
},
"v_tone1": {
"unicode": "270C-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "victory hand tone 1",
"shortname": ":v_tone1:",
"category": "people",
@@ -28240,11 +31574,12 @@
"peace",
"two",
"v"
- ]
+ ],
+ "moji": "✌🏻"
},
"v_tone2": {
"unicode": "270C-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "victory hand tone 2",
"shortname": ":v_tone2:",
"category": "people",
@@ -28256,11 +31591,12 @@
"peace",
"two",
"v"
- ]
+ ],
+ "moji": "✌🏼"
},
"v_tone3": {
"unicode": "270C-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "victory hand tone 3",
"shortname": ":v_tone3:",
"category": "people",
@@ -28272,11 +31608,12 @@
"peace",
"two",
"v"
- ]
+ ],
+ "moji": "✌🏽"
},
"v_tone4": {
"unicode": "270C-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "victory hand tone 4",
"shortname": ":v_tone4:",
"category": "people",
@@ -28288,11 +31625,12 @@
"peace",
"two",
"v"
- ]
+ ],
+ "moji": "✌🏾"
},
"v_tone5": {
"unicode": "270C-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "victory hand tone 5",
"shortname": ":v_tone5:",
"category": "people",
@@ -28304,14 +31642,15 @@
"peace",
"two",
"v"
- ]
+ ],
+ "moji": "✌🏿"
},
"vertical_traffic_light": {
"unicode": "1F6A6",
"unicode_alternates": [],
"name": "vertical traffic light",
"shortname": ":vertical_traffic_light:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -28321,7 +31660,9 @@
"stop",
"go",
"yield",
- "vertical"
+ "vertical",
+ "object",
+ "stop light"
],
"moji": "🚦"
},
@@ -28336,7 +31677,8 @@
"keywords": [
"oldschool",
"record",
- "video"
+ "video",
+ "electronics"
],
"moji": "📼"
},
@@ -28345,12 +31687,13 @@
"unicode_alternates": [],
"name": "vibration mode",
"shortname": ":vibration_mode:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"orange-square",
- "phone"
+ "phone",
+ "symbol"
],
"moji": "📳"
},
@@ -28364,7 +31707,10 @@
"aliases_ascii": [],
"keywords": [
"film",
- "record"
+ "record",
+ "electronics",
+ "camera",
+ "movie"
],
"moji": "📹"
},
@@ -28373,7 +31719,7 @@
"unicode_alternates": [],
"name": "video game",
"shortname": ":video_game:",
- "category": "objects",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -28383,11 +31729,11 @@
"play",
"video",
"game",
- "console",
- "controller",
"nintendo",
"xbox",
- "playstation"
+ "playstation",
+ "electronics",
+ "boys night"
],
"moji": "🎮"
},
@@ -28396,7 +31742,7 @@
"unicode_alternates": [],
"name": "violin",
"shortname": ":violin:",
- "category": "objects",
+ "category": "activity",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -28404,8 +31750,8 @@
"music",
"violin",
"fiddle",
- "music",
- "instrument"
+ "instruments",
+ "sarcastic"
],
"moji": "🎻"
},
@@ -28416,7 +31762,7 @@
],
"name": "virgo",
"shortname": ":virgo:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -28428,9 +31774,8 @@
"constellation",
"stars",
"zodiac",
- "sign",
- "zodiac",
- "horoscope"
+ "horoscope",
+ "symbol"
],
"moji": "♍"
},
@@ -28439,7 +31784,7 @@
"unicode_alternates": [],
"name": "volcano",
"shortname": ":volcano:",
- "category": "nature",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -28449,31 +31794,40 @@
"lava",
"magma",
"hot",
- "explode"
+ "explode",
+ "places",
+ "tropical"
],
"moji": "🌋"
},
"volleyball": {
"unicode": "1F3D0",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "volleyball",
"shortname": ":volleyball:",
"category": "activity",
"aliases": [],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "game",
+ "ball",
+ "sport",
+ "volleyball"
+ ],
+ "moji": "🏐"
},
"vs": {
"unicode": "1F19A",
"unicode_alternates": [],
"name": "squared vs",
"shortname": ":vs:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"orange-square",
- "words"
+ "words",
+ "symbol"
],
"moji": "🆚"
},
@@ -28493,12 +31847,17 @@
"leonard",
"nimoy",
"star trek",
- "live long"
- ]
+ "live long",
+ "body",
+ "hands",
+ "hi",
+ "diversity"
+ ],
+ "moji": "🖖"
},
"vulcan_tone1": {
"unicode": "1F596-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "raised hand with part between middle and ring fingers tone 1",
"shortname": ":vulcan_tone1:",
"category": "people",
@@ -28513,11 +31872,12 @@
"nimoy",
"star trek",
"live long"
- ]
+ ],
+ "moji": "🖖🏻"
},
"vulcan_tone2": {
"unicode": "1F596-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "raised hand with part between middle and ring fingers tone 2",
"shortname": ":vulcan_tone2:",
"category": "people",
@@ -28532,11 +31892,12 @@
"nimoy",
"star trek",
"live long"
- ]
+ ],
+ "moji": "🖖🏼"
},
"vulcan_tone3": {
"unicode": "1F596-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "raised hand with part between middle and ring fingers tone 3",
"shortname": ":vulcan_tone3:",
"category": "people",
@@ -28551,11 +31912,12 @@
"nimoy",
"star trek",
"live long"
- ]
+ ],
+ "moji": "🖖🏽"
},
"vulcan_tone4": {
"unicode": "1F596-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "raised hand with part between middle and ring fingers tone 4",
"shortname": ":vulcan_tone4:",
"category": "people",
@@ -28570,11 +31932,12 @@
"nimoy",
"star trek",
"live long"
- ]
+ ],
+ "moji": "🖖🏾"
},
"vulcan_tone5": {
"unicode": "1F596-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "raised hand with part between middle and ring fingers tone 5",
"shortname": ":vulcan_tone5:",
"category": "people",
@@ -28589,14 +31952,15 @@
"nimoy",
"star trek",
"live long"
- ]
+ ],
+ "moji": "🖖🏿"
},
"walking": {
"unicode": "1F6B6",
"unicode_alternates": [],
"name": "pedestrian",
"shortname": ":walking:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -28607,13 +31971,16 @@
"stroll",
"stride",
"foot",
- "feet"
+ "feet",
+ "people",
+ "men",
+ "diversity"
],
"moji": "🚶"
},
"walking_tone1": {
"unicode": "1F6B6-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "pedestrian tone 1",
"shortname": ":walking_tone1:",
"category": "people",
@@ -28626,11 +31993,12 @@
"stride",
"hiking",
"hike"
- ]
+ ],
+ "moji": "🚶🏻"
},
"walking_tone2": {
"unicode": "1F6B6-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "pedestrian tone 2",
"shortname": ":walking_tone2:",
"category": "people",
@@ -28643,11 +32011,12 @@
"stride",
"hiking",
"hike"
- ]
+ ],
+ "moji": "🚶🏼"
},
"walking_tone3": {
"unicode": "1F6B6-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "pedestrian tone 3",
"shortname": ":walking_tone3:",
"category": "people",
@@ -28660,11 +32029,12 @@
"stride",
"hiking",
"hike"
- ]
+ ],
+ "moji": "🚶🏽"
},
"walking_tone4": {
"unicode": "1F6B6-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "pedestrian tone 4",
"shortname": ":walking_tone4:",
"category": "people",
@@ -28677,11 +32047,12 @@
"stride",
"hiking",
"hike"
- ]
+ ],
+ "moji": "🚶🏾"
},
"walking_tone5": {
"unicode": "1F6B6-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "pedestrian tone 5",
"shortname": ":walking_tone5:",
"category": "people",
@@ -28694,7 +32065,8 @@
"stride",
"hiking",
"hike"
- ]
+ ],
+ "moji": "🚶🏿"
},
"waning_crescent_moon": {
"unicode": "1F318",
@@ -28712,7 +32084,8 @@
"sky",
"night",
"cheese",
- "phase"
+ "phase",
+ "space"
],
"moji": "🌘"
},
@@ -28732,7 +32105,8 @@
"sky",
"night",
"cheese",
- "phase"
+ "phase",
+ "space"
],
"moji": "🌖"
},
@@ -28743,12 +32117,14 @@
],
"name": "warning sign",
"shortname": ":warning:",
- "category": "places",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"exclamation",
- "wip"
+ "wip",
+ "symbol",
+ "punctuation"
],
"moji": "⚠"
},
@@ -28757,14 +32133,17 @@
"unicode_alternates": [],
"name": "wastebasket",
"shortname": ":wastebasket:",
- "category": "objects_symbols",
+ "category": "objects",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"trash",
"garbage",
- "dispose"
- ]
+ "dispose",
+ "object",
+ "work"
+ ],
+ "moji": "🗑"
},
"watch": {
"unicode": "231A",
@@ -28778,7 +32157,8 @@
"aliases_ascii": [],
"keywords": [
"accessories",
- "time"
+ "time",
+ "electronics"
],
"moji": "⌚"
},
@@ -28800,16 +32180,83 @@
"asia",
"bovine",
"milk",
- "dairy"
+ "dairy",
+ "wildlife"
],
"moji": "🐃"
},
+ "water_polo": {
+ "unicode": "1F93D",
+ "unicode_alternates": [],
+ "name": "water polo",
+ "shortname": ":water_polo:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤽"
+ },
+ "water_polo_tone1": {
+ "unicode": "1F93D-1F3FB",
+ "unicode_alternates": [],
+ "name": "water polo tone 1",
+ "shortname": ":water_polo_tone1:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤽🏻"
+ },
+ "water_polo_tone2": {
+ "unicode": "1F93D-1F3FC",
+ "unicode_alternates": [],
+ "name": "water polo tone 2",
+ "shortname": ":water_polo_tone2:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤽🏼"
+ },
+ "water_polo_tone3": {
+ "unicode": "1F93D-1F3FD",
+ "unicode_alternates": [],
+ "name": "water polo tone 3",
+ "shortname": ":water_polo_tone3:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤽🏽"
+ },
+ "water_polo_tone4": {
+ "unicode": "1F93D-1F3FE",
+ "unicode_alternates": [],
+ "name": "water polo tone 4",
+ "shortname": ":water_polo_tone4:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤽🏾"
+ },
+ "water_polo_tone5": {
+ "unicode": "1F93D-1F3FF",
+ "unicode_alternates": [],
+ "name": "water polo tone 5",
+ "shortname": ":water_polo_tone5:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤽🏿"
+ },
"watermelon": {
"unicode": "1F349",
"unicode_alternates": [],
"name": "watermelon",
"shortname": ":watermelon:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -28818,7 +32265,6 @@
"melon",
"watermelon",
"summer",
- "fruit",
"large"
],
"moji": "🍉"
@@ -28828,7 +32274,7 @@
"unicode_alternates": [],
"name": "waving hand sign",
"shortname": ":wave:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -28836,13 +32282,16 @@
"gesture",
"goodbye",
"hands",
- "solong"
+ "solong",
+ "body",
+ "hi",
+ "diversity"
],
"moji": "👋"
},
"wave_tone1": {
"unicode": "1F44B-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "waving hand sign tone 1",
"shortname": ":wave_tone1:",
"category": "people",
@@ -28855,11 +32304,12 @@
"solong",
"hi",
"wave"
- ]
+ ],
+ "moji": "👋🏻"
},
"wave_tone2": {
"unicode": "1F44B-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "waving hand sign tone 2",
"shortname": ":wave_tone2:",
"category": "people",
@@ -28872,11 +32322,12 @@
"solong",
"hi",
"wave"
- ]
+ ],
+ "moji": "👋🏼"
},
"wave_tone3": {
"unicode": "1F44B-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "waving hand sign tone 3",
"shortname": ":wave_tone3:",
"category": "people",
@@ -28889,11 +32340,12 @@
"solong",
"hi",
"wave"
- ]
+ ],
+ "moji": "👋🏽"
},
"wave_tone4": {
"unicode": "1F44B-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "waving hand sign tone 4",
"shortname": ":wave_tone4:",
"category": "people",
@@ -28906,11 +32358,12 @@
"solong",
"hi",
"wave"
- ]
+ ],
+ "moji": "👋🏾"
},
"wave_tone5": {
"unicode": "1F44B-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "waving hand sign tone 5",
"shortname": ":wave_tone5:",
"category": "people",
@@ -28923,19 +32376,21 @@
"solong",
"hi",
"wave"
- ]
+ ],
+ "moji": "👋🏿"
},
"wavy_dash": {
"unicode": "3030",
"unicode_alternates": [],
"name": "wavy dash",
"shortname": ":wavy_dash:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"draw",
- "line"
+ "line",
+ "symbol"
],
"moji": "〰"
},
@@ -28954,7 +32409,8 @@
"sky",
"night",
"cheese",
- "phase"
+ "phase",
+ "space"
],
"moji": "🌒"
},
@@ -28967,7 +32423,10 @@
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "nature"
+ "nature",
+ "space",
+ "sky",
+ "moon"
],
"moji": "🌔"
},
@@ -28976,7 +32435,7 @@
"unicode_alternates": [],
"name": "water closet",
"shortname": ":wc:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -28985,13 +32444,13 @@
"toilet",
"water",
"closet",
- "toilet",
"bathroom",
"throne",
"porcelain",
"waste",
"flush",
- "plumbing"
+ "plumbing",
+ "symbol"
],
"moji": "🚾"
},
@@ -29000,7 +32459,7 @@
"unicode_alternates": [],
"name": "weary face",
"shortname": ":weary:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -29010,13 +32469,14 @@
"sleepy",
"tired",
"weary",
- "sleepy",
- "tired",
"tiredness",
"study",
"finals",
"school",
- "exhausted"
+ "exhausted",
+ "smiley",
+ "stressed",
+ "emotion"
],
"moji": "😩"
},
@@ -29025,7 +32485,7 @@
"unicode_alternates": [],
"name": "wedding",
"shortname": ":wedding:",
- "category": "places",
+ "category": "travel",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -29035,7 +32495,11 @@
"groom",
"like",
"love",
- "marriage"
+ "marriage",
+ "places",
+ "wedding",
+ "building",
+ "parties"
],
"moji": "💒"
},
@@ -29051,7 +32515,10 @@
"animal",
"nature",
"ocean",
- "sea"
+ "sea",
+ "wildlife",
+ "tropical",
+ "whales"
],
"moji": "🐳"
},
@@ -29073,13 +32540,16 @@
"bloated",
"fat",
"large",
- "massive"
+ "massive",
+ "wildlife",
+ "tropical",
+ "whales"
],
"moji": "🐋"
},
"wheel_of_dharma": {
"unicode": "2638",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "wheel of dharma",
"shortname": ":wheel_of_dharma:",
"category": "symbols",
@@ -29089,7 +32559,8 @@
"buddhist",
"religion",
"symbol"
- ]
+ ],
+ "moji": "☸"
},
"wheelchair": {
"unicode": "267F",
@@ -29098,12 +32569,13 @@
],
"name": "wheelchair symbol",
"shortname": ":wheelchair:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"blue-square",
- "disabled"
+ "disabled",
+ "symbol"
],
"moji": "♿"
},
@@ -29112,13 +32584,14 @@
"unicode_alternates": [],
"name": "white heavy check mark",
"shortname": ":white_check_mark:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"agree",
"green-square",
- "ok"
+ "ok",
+ "symbol"
],
"moji": "✅"
},
@@ -29129,11 +32602,14 @@
],
"name": "medium white circle",
"shortname": ":white_circle:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "shape"
+ "shape",
+ "shapes",
+ "symbol",
+ "circle"
],
"moji": "⚪"
},
@@ -29142,7 +32618,7 @@
"unicode_alternates": [],
"name": "white flower",
"shortname": ":white_flower:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -29158,7 +32634,8 @@
"homework",
"student",
"assignment",
- "praise"
+ "praise",
+ "symbol"
],
"moji": "💮"
},
@@ -29169,11 +32646,14 @@
],
"name": "white large square",
"shortname": ":white_large_square:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "shape"
+ "shape",
+ "shapes",
+ "symbol",
+ "square"
],
"moji": "⬜"
},
@@ -29184,11 +32664,14 @@
],
"name": "white medium small square",
"shortname": ":white_medium_small_square:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "shape"
+ "shape",
+ "shapes",
+ "symbol",
+ "square"
],
"moji": "◽"
},
@@ -29199,11 +32682,14 @@
],
"name": "white medium square",
"shortname": ":white_medium_square:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "shape"
+ "shape",
+ "shapes",
+ "symbol",
+ "square"
],
"moji": "◻"
},
@@ -29214,11 +32700,14 @@
],
"name": "white small square",
"shortname": ":white_small_square:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "shape"
+ "shape",
+ "shapes",
+ "symbol",
+ "square"
],
"moji": "▫"
},
@@ -29227,17 +32716,20 @@
"unicode_alternates": [],
"name": "white square button",
"shortname": ":white_square_button:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
- "shape"
+ "shape",
+ "shapes",
+ "symbol",
+ "square"
],
"moji": "🔳"
},
"white_sun_cloud": {
"unicode": "1F325",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "white sun behind cloud",
"shortname": ":white_sun_cloud:",
"category": "nature",
@@ -29247,12 +32739,17 @@
"aliases_ascii": [],
"keywords": [
"nature",
- "weather"
- ]
+ "weather",
+ "sky",
+ "cloud",
+ "cold",
+ "sun"
+ ],
+ "moji": "🌥"
},
"white_sun_rain_cloud": {
"unicode": "1F326",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "white sun behind cloud with rain",
"shortname": ":white_sun_rain_cloud:",
"category": "nature",
@@ -29262,12 +32759,18 @@
"aliases_ascii": [],
"keywords": [
"nature",
- "weather"
- ]
+ "weather",
+ "sky",
+ "cloud",
+ "cold",
+ "rain",
+ "sun"
+ ],
+ "moji": "🌦"
},
"white_sun_small_cloud": {
"unicode": "1F324",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "white sun with small cloud",
"shortname": ":white_sun_small_cloud:",
"category": "nature",
@@ -29277,8 +32780,25 @@
"aliases_ascii": [],
"keywords": [
"nature",
- "weather"
- ]
+ "weather",
+ "sky",
+ "cloud",
+ "sun"
+ ],
+ "moji": "🌤"
+ },
+ "wilted_rose": {
+ "unicode": "1F940",
+ "unicode_alternates": [],
+ "name": "wilted flower",
+ "shortname": ":wilted_rose:",
+ "category": "nature",
+ "aliases": [
+ ":wilted_flower:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🥀"
},
"wind_blowing_face": {
"unicode": "1F32C",
@@ -29290,8 +32810,11 @@
"aliases_ascii": [],
"keywords": [
"mother",
- "nature"
- ]
+ "nature",
+ "weather",
+ "cold"
+ ],
+ "moji": "🌬"
},
"wind_chime": {
"unicode": "1F390",
@@ -29314,7 +32837,9 @@
"soothing",
"protective",
"spiritual",
- "sound"
+ "sound",
+ "object",
+ "japan"
],
"moji": "🎐"
},
@@ -29323,7 +32848,7 @@
"unicode_alternates": [],
"name": "wine glass",
"shortname": ":wine_glass:",
- "category": "objects",
+ "category": "food",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -29338,7 +32863,10 @@
"grapes",
"tasting",
"wine",
- "winery"
+ "winery",
+ "italian",
+ "girls night",
+ "parties"
],
"moji": "🍷"
},
@@ -29347,7 +32875,7 @@
"unicode_alternates": [],
"name": "winking face",
"shortname": ":wink:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [
";)",
@@ -29367,7 +32895,10 @@
"wink",
"winking",
"friendly",
- "joke"
+ "joke",
+ "silly",
+ "smiley",
+ "emotion"
],
"moji": "😉"
},
@@ -29381,7 +32912,9 @@
"aliases_ascii": [],
"keywords": [
"animal",
- "nature"
+ "nature",
+ "wildlife",
+ "roar"
],
"moji": "🐺"
},
@@ -29390,18 +32923,25 @@
"unicode_alternates": [],
"name": "woman",
"shortname": ":woman:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"female",
- "girls"
+ "girls",
+ "people",
+ "women",
+ "sex",
+ "diversity",
+ "feminist",
+ "selfie",
+ "girls night"
],
"moji": "👩"
},
"woman_tone1": {
"unicode": "1F469-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "woman tone 1",
"shortname": ":woman_tone1:",
"category": "people",
@@ -29411,11 +32951,12 @@
"female",
"girl",
"lady"
- ]
+ ],
+ "moji": "👩🏻"
},
"woman_tone2": {
"unicode": "1F469-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "woman tone 2",
"shortname": ":woman_tone2:",
"category": "people",
@@ -29425,11 +32966,12 @@
"female",
"girl",
"lady"
- ]
+ ],
+ "moji": "👩🏼"
},
"woman_tone3": {
"unicode": "1F469-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "woman tone 3",
"shortname": ":woman_tone3:",
"category": "people",
@@ -29439,11 +32981,12 @@
"female",
"girl",
"lady"
- ]
+ ],
+ "moji": "👩🏽"
},
"woman_tone4": {
"unicode": "1F469-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "woman tone 4",
"shortname": ":woman_tone4:",
"category": "people",
@@ -29453,11 +32996,12 @@
"female",
"girl",
"lady"
- ]
+ ],
+ "moji": "👩🏾"
},
"woman_tone5": {
"unicode": "1F469-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "woman tone 5",
"shortname": ":woman_tone5:",
"category": "people",
@@ -29467,14 +33011,15 @@
"female",
"girl",
"lady"
- ]
+ ],
+ "moji": "👩🏿"
},
"womans_clothes": {
"unicode": "1F45A",
"unicode_alternates": [],
"name": "womans clothes",
"shortname": ":womans_clothes:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -29490,7 +33035,8 @@
"shopping",
"shop",
"dressing",
- "dressed"
+ "dressed",
+ "women"
],
"moji": "👚"
},
@@ -29499,13 +33045,14 @@
"unicode_alternates": [],
"name": "womans hat",
"shortname": ":womans_hat:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"accessories",
"fashion",
- "female"
+ "female",
+ "women"
],
"moji": "👒"
},
@@ -29514,7 +33061,7 @@
"unicode_alternates": [],
"name": "womens symbol",
"shortname": ":womens:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -29525,7 +33072,8 @@
"sign",
"girl",
"female",
- "avatar"
+ "avatar",
+ "symbol"
],
"moji": "🚺"
},
@@ -29534,7 +33082,7 @@
"unicode_alternates": [],
"name": "worried face",
"shortname": ":worried:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -29544,8 +33092,10 @@
"worried",
"anxious",
"distressed",
- "nervous",
- "tense"
+ "tense",
+ "sad",
+ "smiley",
+ "emotion"
],
"moji": "😟"
},
@@ -29560,30 +33110,111 @@
"keywords": [
"diy",
"ikea",
- "tools"
+ "tools",
+ "object",
+ "tool"
],
"moji": "🔧"
},
- "writing_hand": {
- "unicode": "1F58E",
+ "wrestlers": {
+ "unicode": "1F93C",
"unicode_alternates": [],
- "name": "left writing hand",
- "shortname": ":writing_hand:",
- "category": "people",
+ "name": "wrestlers",
+ "shortname": ":wrestlers:",
+ "category": "activity",
+ "aliases": [
+ ":wrestling:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤼"
+ },
+ "wrestlers_tone1": {
+ "unicode": "1F93C-1F3FB",
+ "unicode_alternates": [],
+ "name": "wrestlers tone 1",
+ "shortname": ":wrestlers_tone1:",
+ "category": "activity",
+ "aliases": [
+ ":wrestling_tone1:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤼🏻"
+ },
+ "wrestlers_tone2": {
+ "unicode": "1F93C-1F3FC",
+ "unicode_alternates": [],
+ "name": "wrestlers tone 2",
+ "shortname": ":wrestlers_tone2:",
+ "category": "activity",
"aliases": [
- ":left_writing_hand:"
+ ":wrestling_tone2:"
],
"aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤼🏼"
+ },
+ "wrestlers_tone3": {
+ "unicode": "1F93C-1F3FD",
+ "unicode_alternates": [],
+ "name": "wrestlers tone 3",
+ "shortname": ":wrestlers_tone3:",
+ "category": "activity",
+ "aliases": [
+ ":wrestling_tone3:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤼🏽"
+ },
+ "wrestlers_tone4": {
+ "unicode": "1F93C-1F3FE",
+ "unicode_alternates": [],
+ "name": "wrestlers tone 4",
+ "shortname": ":wrestlers_tone4:",
+ "category": "activity",
+ "aliases": [
+ ":wrestling_tone4:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤼🏾"
+ },
+ "wrestlers_tone5": {
+ "unicode": "1F93C-1F3FF",
+ "unicode_alternates": [],
+ "name": "wrestlers tone 5",
+ "shortname": ":wrestlers_tone5:",
+ "category": "activity",
+ "aliases": [
+ ":wrestling_tone5:"
+ ],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🤼🏿"
+ },
+ "writing_hand": {
+ "unicode": "270D",
+ "unicode_alternates": [
+ "270D-FE0F"
+ ],
+ "name": "writing hand",
+ "shortname": ":writing_hand:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
"keywords": [
+ "body",
+ "hands",
"write",
- "sign",
- "signature",
- "draw"
- ]
+ "diversity"
+ ],
+ "moji": "✍"
},
"writing_hand_tone1": {
"unicode": "270D-1F3FB",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "writing hand tone 1",
"shortname": ":writing_hand_tone1:",
"category": "people",
@@ -29594,11 +33225,12 @@
"sign",
"signature",
"draw"
- ]
+ ],
+ "moji": "✍🏻"
},
"writing_hand_tone2": {
"unicode": "270D-1F3FC",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "writing hand tone 2",
"shortname": ":writing_hand_tone2:",
"category": "people",
@@ -29609,11 +33241,12 @@
"sign",
"signature",
"draw"
- ]
+ ],
+ "moji": "✍🏼"
},
"writing_hand_tone3": {
"unicode": "270D-1F3FD",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "writing hand tone 3",
"shortname": ":writing_hand_tone3:",
"category": "people",
@@ -29624,11 +33257,12 @@
"sign",
"signature",
"draw"
- ]
+ ],
+ "moji": "✍🏽"
},
"writing_hand_tone4": {
"unicode": "270D-1F3FE",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "writing hand tone 4",
"shortname": ":writing_hand_tone4:",
"category": "people",
@@ -29639,11 +33273,12 @@
"sign",
"signature",
"draw"
- ]
+ ],
+ "moji": "✍🏾"
},
"writing_hand_tone5": {
"unicode": "270D-1F3FF",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "writing hand tone 5",
"shortname": ":writing_hand_tone5:",
"category": "people",
@@ -29654,20 +33289,23 @@
"sign",
"signature",
"draw"
- ]
+ ],
+ "moji": "✍🏿"
},
"x": {
"unicode": "274C",
"unicode_alternates": [],
"name": "cross mark",
"shortname": ":x:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"delete",
"no",
- "remove"
+ "remove",
+ "symbol",
+ "sol"
],
"moji": "❌"
},
@@ -29676,7 +33314,7 @@
"unicode_alternates": [],
"name": "yellow heart",
"shortname": ":yellow_heart:",
- "category": "emoticons",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -29687,7 +33325,6 @@
"yellow",
"gold",
"heart",
- "love",
"friendship",
"happy",
"happiness",
@@ -29696,7 +33333,8 @@
"respectful",
"honest",
"caring",
- "selfless"
+ "selfless",
+ "symbol"
],
"moji": "💛"
},
@@ -29715,10 +33353,7 @@
"money",
"yen",
"japan",
- "japanese",
"banknote",
- "money",
- "currency",
"paper",
"cash",
"bill"
@@ -29727,7 +33362,7 @@
},
"yin_yang": {
"unicode": "262F",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "yin yang",
"shortname": ":yin_yang:",
"category": "symbols",
@@ -29739,14 +33374,15 @@
"symbol",
"tao",
"taoist"
- ]
+ ],
+ "moji": "☯"
},
"yum": {
"unicode": "1F60B",
"unicode_alternates": [],
"name": "face savouring delicious food",
"shortname": ":yum:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
@@ -29762,7 +33398,12 @@
"yummy",
"yum",
"tasty",
- "savory"
+ "savory",
+ "silly",
+ "smiley",
+ "emotion",
+ "sarcastic",
+ "good"
],
"moji": "😋"
},
@@ -29779,7 +33420,9 @@
"keywords": [
"lightning bolt",
"thunder",
- "weather"
+ "weather",
+ "sky",
+ "diarrhea"
],
"moji": "⚡"
},
@@ -29789,20 +33432,23 @@
"unicode_alternates": [
"0030-FE0F-20E3"
],
- "name": "digit zero",
+ "name": "keycap digit zero",
"shortname": ":zero:",
- "category": "other",
+ "category": "symbols",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"blue-square",
"null",
- "numbers"
+ "numbers",
+ "number",
+ "math",
+ "symbol"
]
},
"zipper_mouth": {
"unicode": "1F910",
- "unicode_alternates": "",
+ "unicode_alternates": [],
"name": "zipper-mouth face",
"shortname": ":zipper_mouth:",
"category": "people",
@@ -29810,19 +33456,24 @@
":zipper_mouth_face:"
],
"aliases_ascii": [],
- "keywords": []
+ "keywords": [
+ "mad",
+ "smiley"
+ ],
+ "moji": "🤐"
},
"zzz": {
"unicode": "1F4A4",
"unicode_alternates": [],
"name": "sleeping symbol",
"shortname": ":zzz:",
- "category": "emoticons",
+ "category": "people",
"aliases": [],
"aliases_ascii": [],
"keywords": [
"sleepy",
- "tired"
+ "tired",
+ "goodnight"
],
"moji": "💤"
}
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 0e7a1cc2623..3d7d67510a8 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -33,14 +33,13 @@ module API
mount ::API::Commits
mount ::API::DeployKeys
mount ::API::Files
- mount ::API::Gitignores
mount ::API::GroupMembers
mount ::API::Groups
mount ::API::Internal
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 +57,8 @@ module API
mount ::API::Subscriptions
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..2efe7e3adf3 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:
@@ -57,9 +56,9 @@ module API
not_found!('Award Emoji') unless can_read_awardable?
- award = awardable.award_emoji.new(name: params[:name], user: current_user)
+ award = awardable.create_award_emoji(params[:name], current_user)
- if award.save
+ if award.persisted?
present award, with: Entities::AwardEmoji
else
not_found!("Award Emoji #{award.errors.messages}")
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index 231840148d9..66b853eb342 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -15,7 +15,8 @@ module API
# GET /projects/:id/repository/branches
get ":id/repository/branches" do
branches = user_project.repository.branches.sort_by(&:name)
- present branches, with: Entities::RepoObject, project: user_project
+
+ present branches, with: Entities::RepoBranch, project: user_project
end
# Get a single branch
@@ -25,10 +26,11 @@ 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
+
+ present @branch, with: Entities::RepoBranch, project: user_project
end
# Protect a single branch
@@ -36,19 +38,31 @@ module API
# Parameters:
# id (required) - The ID of a project
# branch (required) - The name of the branch
+ # developers_can_push (optional) - Flag if developers can push to that branch
+ # developers_can_merge (optional) - Flag if developers can merge to that branch
# 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])
- not_found!("Branch") unless @branch
+ not_found!('Branch') unless @branch
protected_branch = user_project.protected_branches.find_by(name: @branch.name)
- user_project.protected_branches.create(name: @branch.name) unless protected_branch
+ developers_can_push = to_boolean(params[:developers_can_push])
+ developers_can_merge = to_boolean(params[:developers_can_merge])
- present @branch, with: Entities::RepoObject, project: user_project
+ if protected_branch
+ protected_branch.developers_can_push = developers_can_push unless developers_can_push.nil?
+ protected_branch.developers_can_merge = developers_can_merge unless developers_can_merge.nil?
+ protected_branch.save
+ else
+ user_project.protected_branches.create(name: @branch.name,
+ developers_can_push: developers_can_push || false,
+ developers_can_merge: developers_can_merge || false)
+ end
+
+ present @branch, with: Entities::RepoBranch, project: user_project
end
# Unprotect a single branch
@@ -59,8 +73,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])
@@ -68,7 +81,7 @@ module API
protected_branch = user_project.protected_branches.find_by(name: @branch.name)
protected_branch.destroy if protected_branch
- present @branch, with: Entities::RepoObject, project: user_project
+ present @branch, with: Entities::RepoBranch, project: user_project
end
# Create branch
@@ -86,7 +99,7 @@ module API
if result[:status] == :success
present result[:branch],
- with: Entities::RepoObject,
+ with: Entities::RepoBranch,
project: user_project
else
render_api_error!(result[:message], 400)
@@ -101,7 +114,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/commit_statuses.rb b/lib/api/commit_statuses.rb
index 323a7086890..acb4812b5cf 100644
--- a/lib/api/commit_statuses.rb
+++ b/lib/api/commit_statuses.rb
@@ -64,7 +64,7 @@ module API
ref = branches.first
end
- pipeline = @project.ensure_pipeline(commit.sha, ref)
+ pipeline = @project.ensure_pipeline(commit.sha, ref, current_user)
name = params[:name] || params[:context]
status = GenericCommitStatus.running_or_pending.find_by(pipeline: pipeline, name: name, ref: params[:ref])
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 2e397643ed1..d7e74582459 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -54,10 +54,19 @@ module API
class BasicProjectDetails < Grape::Entity
expose :id
+ expose :http_url_to_repo, :web_url
expose :name, :name_with_namespace
expose :path, :path_with_namespace
end
+ class SharedGroup < Grape::Entity
+ expose :group_id
+ expose :group_name do |group_link, options|
+ group_link.group.name
+ end
+ expose :group_access, as: :group_access_level
+ end
+
class Project < Grape::Entity
expose :id, :description, :default_branch, :tag_list
expose :public?, as: :public
@@ -77,6 +86,9 @@ module API
expose :open_issues_count, if: lambda { |project, options| project.issues_enabled? && project.default_issues_tracker? }
expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] }
expose :public_builds
+ expose :shared_with_groups do |project, options|
+ SharedGroup.represent(project.project_group_links.all, options)
+ end
end
class ProjectMember < UserBasic
@@ -93,6 +105,7 @@ module API
class GroupDetail < Group
expose :projects, using: Entities::Project
+ expose :shared_projects, using: Entities::Project
end
class GroupMember < UserBasic
@@ -101,21 +114,23 @@ module API
end
end
- class RepoObject < Grape::Entity
+ class RepoBranch < Grape::Entity
expose :name
- expose :commit do |repo_obj, options|
- if repo_obj.respond_to?(:commit)
- repo_obj.commit
- elsif options[:project]
- options[:project].repository.commit(repo_obj.target)
- end
+ expose :commit do |repo_branch, options|
+ options[:project].repository.commit(repo_branch.target)
end
- expose :protected do |repo, options|
- if options[:project]
- options[:project].protected_branch? repo.name
- end
+ expose :protected do |repo_branch, options|
+ options[:project].protected_branch? repo_branch.name
+ end
+
+ expose :developers_can_push do |repo_branch, options|
+ options[:project].developers_can_push_to_protected_branch? repo_branch.name
+ end
+
+ expose :developers_can_merge do |repo_branch, options|
+ options[:project].developers_can_merge_to_protected_branch? repo_branch.name
end
end
@@ -174,6 +189,7 @@ module API
end
expose :user_notes_count
expose :upvotes, :downvotes
+ expose :due_date
end
class ExternalIssue < Grape::Entity
@@ -187,7 +203,6 @@ module API
expose :author, :assignee, using: Entities::UserBasic
expose :source_project_id, :target_project_id
expose :label_names, as: :labels
- expose :description
expose :work_in_progress?, as: :work_in_progress
expose :milestone, using: Entities::Milestone
expose :merge_when_build_succeeds
@@ -196,6 +211,8 @@ module API
merge_request.subscribed?(options[:current_user])
end
expose :user_notes_count
+ expose :should_remove_source_branch?, as: :should_remove_source_branch
+ expose :force_remove_source_branch?, as: :force_remove_source_branch
end
class MergeRequestChanges < MergeRequest
@@ -240,9 +257,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 +289,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 +418,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
@@ -384,27 +427,14 @@ module API
end
class RepoTag < Grape::Entity
- expose :name
- expose :message do |repo_obj, _options|
- if repo_obj.respond_to?(:message)
- repo_obj.message
- else
- nil
- end
- end
+ expose :name, :message
- expose :commit do |repo_obj, options|
- if repo_obj.respond_to?(:commit)
- repo_obj.commit
- elsif options[:project]
- options[:project].repository.commit(repo_obj.target)
- end
+ expose :commit do |repo_tag, options|
+ options[:project].repository.commit(repo_tag.target)
end
- expose :release, using: Entities::Release do |repo_obj, options|
- if options[:project]
- options[:project].releases.find_by(tag: repo_obj.name)
- end
+ expose :release, using: Entities::Release do |repo_tag, options|
+ options[:project].releases.find_by(tag: repo_tag.name)
end
end
@@ -423,6 +453,7 @@ module API
class RunnerDetails < Runner
expose :tag_list
expose :run_untagged
+ expose :locked
expose :version, :revision, :platform, :architecture
expose :contacted_at
expose :token, if: lambda { |runner, options| options[:current_user].is_admin? || !runner.is_shared? }
@@ -444,11 +475,7 @@ module API
expose :created_at, :started_at, :finished_at
expose :user, with: User
expose :artifacts_file, using: BuildArtifactFile, if: -> (build, opts) { build.artifacts? }
- expose :commit, with: RepoCommit do |repo_obj, _options|
- if repo_obj.respond_to?(:commit)
- repo_obj.commit.commit_data
- end
- end
+ expose :commit, with: RepoCommit
expose :runner, with: Runner
end
@@ -472,11 +499,11 @@ module API
expose :content
end
- class GitignoresList < Grape::Entity
+ class TemplatesList < Grape::Entity
expose :name
end
- class Gitignore < Grape::Entity
+ class Template < Grape::Entity
expose :name, :content
end
end
diff --git a/lib/api/gitignores.rb b/lib/api/gitignores.rb
deleted file mode 100644
index 270c9501dd2..00000000000
--- a/lib/api/gitignores.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-module API
- class Gitignores < Grape::API
-
- # Get the list of the available gitignore templates
- #
- # Example Request:
- # GET /gitignores
- get 'gitignores' do
- present Gitlab::Gitignore.all, with: Entities::GitignoresList
- end
-
- # Get the text for a specific gitignore
- #
- # Parameters:
- # name (required) - The name of a license
- #
- # Example Request:
- # GET /gitignores/Elixir
- #
- get 'gitignores/:name' do
- required_attributes! [:name]
-
- gitignore = Gitlab::Gitignore.find(params[:name])
- not_found!('.gitignore') unless gitignore
-
- present gitignore, with: Entities::Gitignore
- end
- end
-end
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/helpers.rb b/lib/api/helpers.rb
index 77e407b54c5..d6e4eb2afd7 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -9,6 +9,13 @@ module API
[ true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON' ].include?(value)
end
+ def to_boolean(value)
+ return true if value =~ /^(true|t|yes|y|1|on)$/i
+ return false if value =~ /^(false|f|no|n|0|off)$/i
+
+ nil
+ end
+
def find_user_by_private_token
token_string = (params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER]).to_s
User.find_by_authentication_token(token_string) || User.find_by_personal_access_token(token_string)
@@ -17,7 +24,7 @@ module API
def current_user
@current_user ||= (find_user_by_private_token || doorkeeper_guard)
- unless @current_user && Gitlab::UserAccess.allowed?(@current_user)
+ unless @current_user && Gitlab::UserAccess.new(@current_user).allowed?
return nil
end
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index 3ac7b50c4ce..959b700de78 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -13,6 +13,7 @@ module API
# action - git action (git-upload-pack or git-receive-pack)
# ref - branch name
# forced_push - forced_push
+ # protocol - Git access protocol being used, e.g. HTTP or SSH
#
helpers do
@@ -20,11 +21,23 @@ 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
- Gitlab::Metrics.action = 'Grape#/internal/allowed'
-
status 200
actor =
@@ -34,24 +47,31 @@ 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] =
+ if wiki?
+ project.wiki.repository.path_to_repo
+ else
+ project.repository.path_to_repo
+ end
+ end
+
+ response
end
#
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 4c43257c48a..c588103e517 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -59,6 +59,41 @@ module API
end
end
+ resource :groups do
+ # Get a list of group issues
+ #
+ # Parameters:
+ # id (required) - The ID of a group
+ # state (optional) - Return "opened" or "closed" issues
+ # labels (optional) - Comma-separated list of label names
+ # milestone (optional) - Milestone title
+ # order_by (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at`
+ # sort (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
+ #
+ # Example Requests:
+ # GET /groups/:id/issues
+ # GET /groups/:id/issues?state=opened
+ # GET /groups/:id/issues?state=closed
+ # GET /groups/:id/issues?labels=foo
+ # GET /groups/:id/issues?labels=foo,bar
+ # GET /groups/:id/issues?labels=foo,bar&state=opened
+ # GET /groups/:id/issues?milestone=1.0.0
+ # GET /groups/:id/issues?milestone=1.0.0&state=closed
+ get ":id/issues" do
+ group = find_group(params[:id])
+
+ params[:state] ||= 'opened'
+ params[:group_id] = group.id
+ params[:milestone_title] = params.delete(:milestone)
+ params[:label_name] = params.delete(:labels)
+ params[:sort] = "#{params.delete(:order_by)}_#{params.delete(:sort)}" if params[:order_by] && params[:sort]
+
+ issues = IssuesFinder.new(current_user, params).execute
+
+ present paginate(issues), with: Entities::Issue, current_user: current_user
+ end
+ end
+
resource :projects do
# Get a list of project issues
#
@@ -117,12 +152,13 @@ module API
# milestone_id (optional) - The ID of a milestone to assign issue
# labels (optional) - The labels of an issue
# created_at (optional) - Date time string, ISO 8601 formatted
+ # due_date (optional) - Date time string in the format YEAR-MONTH-DAY
# Example Request:
# POST /projects/:id/issues
- post ":id/issues" do
+ post ':id/issues' do
required_attributes! [:title]
- keys = [:title, :description, :assignee_id, :milestone_id]
+ keys = [:title, :description, :assignee_id, :milestone_id, :due_date]
keys << :created_at if current_user.admin? || user_project.owner == current_user
attrs = attributes_for_keys(keys)
@@ -166,12 +202,13 @@ module API
# labels (optional) - The labels of an issue
# state_event (optional) - The state event of an issue (close|reopen)
# updated_at (optional) - Date time string, ISO 8601 formatted
+ # due_date (optional) - Date time string in the format YEAR-MONTH-DAY
# Example Request:
# PUT /projects/:id/issues/:issue_id
- put ":id/issues/:issue_id" do
+ put ':id/issues/:issue_id' do
issue = user_project.issues.find(params[:issue_id])
authorize! :update_issue, issue
- keys = [:title, :description, :assignee_id, :milestone_id, :state_event]
+ keys = [:title, :description, :assignee_id, :milestone_id, :state_event, :due_date]
keys << :updated_at if current_user.admin? || user_project.owner == current_user
attrs = attributes_for_keys(keys)
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..6d2a6f3946c 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -25,7 +25,11 @@ module API
@projects = current_user.authorized_projects
@projects = filter_projects(@projects)
@projects = paginate @projects
- present @projects, with: Entities::ProjectWithAccess, user: current_user
+ if params[:simple]
+ present @projects, with: Entities::BasicProjectDetails, user: current_user
+ else
+ present @projects, with: Entities::ProjectWithAccess, user: current_user
+ end
end
# Get an owned projects list for authenticated user
@@ -341,7 +345,6 @@ module API
else
not_found!("Source Project")
end
-
end
# Remove a forked_from relationship
@@ -418,7 +421,6 @@ module API
present paginate(projects), with: Entities::Project
end
-
# Get a users list
#
# Example Request:
diff --git a/lib/api/runners.rb b/lib/api/runners.rb
index 4faba9dc87b..ecc8f2fc5a2 100644
--- a/lib/api/runners.rb
+++ b/lib/api/runners.rb
@@ -49,7 +49,7 @@ module API
runner = get_runner(params[:id])
authenticate_update_runner!(runner)
- attrs = attributes_for_keys [:description, :active, :tag_list, :run_untagged]
+ attrs = attributes_for_keys [:description, :active, :tag_list, :run_untagged, :locked]
if runner.update(attrs)
present runner, with: Entities::RunnerDetails, current_user: current_user
else
@@ -96,9 +96,14 @@ module API
runner = get_runner(params[:runner_id])
authenticate_enable_runner!(runner)
- Ci::RunnerProject.create(runner: runner, project: user_project)
- present runner, with: Entities::Runner
+ runner_project = runner.assign_to(user_project)
+
+ if runner_project.persisted?
+ present runner, with: Entities::Runner
+ else
+ conflict!("Runner was already enabled for this project")
+ end
end
# Disable project's runner
@@ -163,6 +168,7 @@ module API
def authenticate_enable_runner!(runner)
forbidden!("Runner is shared") if runner.is_shared?
+ forbidden!("Runner is locked") if runner.locked?
return if current_user.is_admin?
forbidden!("No access granted") unless user_can_access_runner?(runner)
end
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/templates.rb b/lib/api/templates.rb
new file mode 100644
index 00000000000..18408797756
--- /dev/null
+++ b/lib/api/templates.rb
@@ -0,0 +1,36 @@
+module API
+ class Templates < Grape::API
+ TEMPLATE_TYPES = {
+ gitignores: Gitlab::Template::Gitignore,
+ gitlab_ci_ymls: Gitlab::Template::GitlabCiYml
+ }.freeze
+
+ TEMPLATE_TYPES.each do |template, klass|
+ # Get the list of the available template
+ #
+ # Example Request:
+ # GET /gitignores
+ # GET /gitlab_ci_ymls
+ get template.to_s do
+ present klass.all, with: Entities::TemplatesList
+ end
+
+ # Get the text for a specific template
+ #
+ # Parameters:
+ # name (required) - The name of a template
+ #
+ # Example Request:
+ # GET /gitignores/Elixir
+ # GET /gitlab_ci_ymls/Ruby
+ get "#{template}/:name" do
+ required_attributes! [:name]
+
+ new_template = klass.find(params[:name])
+ not_found!(template.to_s.singularize) unless new_template
+
+ present new_template, with: Entities::Template
+ end
+ end
+ end
+end
diff --git a/lib/api/todos.rb b/lib/api/todos.rb
new file mode 100644
index 00000000000..26c24c3baff
--- /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)
+
+ todos.length
+ 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.rb b/lib/banzai.rb
index b467413a7dd..9ebe379f454 100644
--- a/lib/banzai.rb
+++ b/lib/banzai.rb
@@ -3,12 +3,12 @@ module Banzai
Renderer.render(text, context)
end
- def self.render_result(text, context = {})
- Renderer.render_result(text, context)
+ def self.cache_collection_render(texts_and_contexts)
+ Renderer.cache_collection_render(texts_and_contexts)
end
- def self.pre_process(text, context)
- Renderer.pre_process(text, context)
+ def self.render_result(text, context = {})
+ Renderer.render_result(text, context)
end
def self.post_process(html, context)
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/autolink_filter.rb b/lib/banzai/filter/autolink_filter.rb
index fac7dad3243..9ed45707515 100644
--- a/lib/banzai/filter/autolink_filter.rb
+++ b/lib/banzai/filter/autolink_filter.rb
@@ -56,6 +56,8 @@ module Banzai
# period (e.g., http://localhost:3000/)
rinku = Rinku.auto_link(html, :urls, options, IGNORE_PARENTS.to_a, 1)
+ return if rinku == html
+
# Rinku returns a String, so parse it back to a Nokogiri::XML::Document
# for further processing.
@doc = parse_html(rinku)
diff --git a/lib/banzai/filter/blockquote_fence_filter.rb b/lib/banzai/filter/blockquote_fence_filter.rb
new file mode 100644
index 00000000000..d2c4b1e4d76
--- /dev/null
+++ b/lib/banzai/filter/blockquote_fence_filter.rb
@@ -0,0 +1,71 @@
+module Banzai
+ module Filter
+ class BlockquoteFenceFilter < HTML::Pipeline::TextFilter
+ REGEX = %r{
+ (?<code>
+ # Code blocks:
+ # ```
+ # Anything, including `>>>` blocks which are ignored by this filter
+ # ```
+
+ ^```
+ .+?
+ \n```$
+ )
+ |
+ (?<html>
+ # HTML block:
+ # <tag>
+ # Anything, including `>>>` blocks which are ignored by this filter
+ # </tag>
+
+ ^<[^>]+?>\n
+ .+?
+ \n<\/[^>]+?>$
+ )
+ |
+ (?:
+ # Blockquote:
+ # >>>
+ # Anything, including code and HTML blocks
+ # >>>
+
+ ^>>>\n
+ (?<quote>
+ (?:
+ # Any character that doesn't introduce a code or HTML block
+ (?!
+ ^```
+ |
+ ^<[^>]+?>\n
+ )
+ .
+ |
+ # A code block
+ \g<code>
+ |
+ # An HTML block
+ \g<html>
+ )+?
+ )
+ \n>>>$
+ )
+ }mx.freeze
+
+ def initialize(text, context = nil, result = nil)
+ super text, context, result
+ @text = @text.delete("\r")
+ end
+
+ def call
+ @text.gsub(REGEX) do
+ if $~[:quote]
+ $~[:quote].gsub(/^/, "> ").gsub(/^> $/, ">")
+ else
+ $~[0]
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/emoji_filter.rb b/lib/banzai/filter/emoji_filter.rb
index d25de900674..ae7d31cf191 100644
--- a/lib/banzai/filter/emoji_filter.rb
+++ b/lib/banzai/filter/emoji_filter.rb
@@ -61,7 +61,7 @@ module Banzai
# Build a regexp that matches all valid :emoji: names.
def self.emoji_pattern
- @emoji_pattern ||= /:(#{Emoji.emojis_names.map { |name| Regexp.escape(name) }.join('|')}):/
+ @emoji_pattern ||= /:(#{Gitlab::Emoji.emojis_names.map { |name| Regexp.escape(name) }.join('|')}):/
end
def emoji_pattern
@@ -69,7 +69,7 @@ module Banzai
end
def emoji_filename(name)
- "#{Emoji.emoji_filename(name)}.png"
+ "#{Gitlab::Emoji.emoji_filename(name)}.png"
end
end
end
diff --git a/lib/banzai/filter/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 2614261f9eb..4042e9a4c25 100644
--- a/lib/banzai/filter/issue_reference_filter.rb
+++ b/lib/banzai/filter/issue_reference_filter.rb
@@ -31,10 +31,14 @@ module Banzai
projects_per_reference.each do |path, project|
issue_ids = references_per_project[path]
- next unless project.default_issues_tracker?
+ if project.default_issues_tracker?
+ issues = project.issues.where(iid: issue_ids.to_a)
+ else
+ issues = issue_ids.map { |id| ExternalIssue.new(id, project) }
+ end
- project.issues.where(iid: issue_ids.to_a).each do |issue|
- hash[project][issue.iid] = issue
+ issues.each do |issue|
+ hash[project][issue.iid.to_i] = issue
end
end
@@ -42,6 +46,26 @@ module Banzai
end
end
+ def object_link_title(object)
+ if object.is_a?(ExternalIssue)
+ "Issue in #{object.project.external_issue_tracker.title}"
+ else
+ super
+ end
+ end
+
+ def data_attributes_for(text, project, object)
+ if object.is_a?(ExternalIssue)
+ data_attribute(
+ project: project.id,
+ external_issue: object.id,
+ reference_type: ExternalIssueReferenceFilter.reference_type
+ )
+ else
+ super
+ end
+ end
+
def find_projects_for_paths(paths)
super(paths).includes(:gitlab_issue_tracker_service)
end
diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb
index 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/redactor_filter.rb b/lib/banzai/filter/redactor_filter.rb
index c753a84a20d..c59a80dd1c7 100644
--- a/lib/banzai/filter/redactor_filter.rb
+++ b/lib/banzai/filter/redactor_filter.rb
@@ -7,40 +7,13 @@ module Banzai
#
class RedactorFilter < HTML::Pipeline::Filter
def call
- nodes = Querying.css(doc, 'a.gfm[data-reference-type]')
- visible = nodes_visible_to_user(nodes)
-
- nodes.each do |node|
- unless visible.include?(node)
- # The reference should be replaced by the original text,
- # which is not always the same as the rendered text.
- text = node.attr('data-original') || node.text
- node.replace(text)
- end
- end
+ Redactor.new(project, current_user).redact([doc])
doc
end
private
- def nodes_visible_to_user(nodes)
- per_type = Hash.new { |h, k| h[k] = [] }
- visible = Set.new
-
- nodes.each do |node|
- per_type[node.attr('data-reference-type')] << node
- end
-
- per_type.each do |type, nodes|
- parser = Banzai::ReferenceParser[type].new(project, current_user)
-
- visible.merge(parser.nodes_visible_to_user(current_user, nodes))
- end
-
- visible
- end
-
def current_user
context[:current_user]
end
diff --git a/lib/banzai/filter/reference_filter.rb b/lib/banzai/filter/reference_filter.rb
index 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/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb
index ea21c7b041c..c78da404607 100644
--- a/lib/banzai/filter/relative_link_filter.rb
+++ b/lib/banzai/filter/relative_link_filter.rb
@@ -14,6 +14,8 @@ module Banzai
def call
return doc unless linkable_files?
+ @uri_types = {}
+
doc.search('a:not(.gfm)').each do |el|
process_link_attr el.attribute('href')
end
@@ -48,7 +50,7 @@ module Banzai
uri.path = [
relative_url_root,
context[:project].path_with_namespace,
- path_type(file_path),
+ uri_type(file_path),
ref || context[:project].default_branch, # if no ref exists, point to the default branch
file_path
].compact.join('/').squeeze('/').chomp('/')
@@ -87,7 +89,7 @@ module Banzai
return path unless request_path
parts = request_path.split('/')
- parts.pop if path_type(request_path) != 'tree'
+ parts.pop if uri_type(request_path) != :tree
while path.start_with?('../')
parts.pop
@@ -98,45 +100,20 @@ module Banzai
end
def file_exists?(path)
- return false if path.nil?
- repository.blob_at(current_sha, path).present? ||
- repository.tree(current_sha, path).entries.any?
- end
-
- # Get the type of the given path
- #
- # path - String path to check
- #
- # Examples:
- #
- # path_type('doc/README.md') # => 'blob'
- # path_type('doc/logo.png') # => 'raw'
- # path_type('doc/api') # => 'tree'
- #
- # Returns a String
- def path_type(path)
- unescaped_path = Addressable::URI.unescape(path)
-
- if tree?(unescaped_path)
- 'tree'
- elsif image?(unescaped_path)
- 'raw'
- else
- 'blob'
- end
+ path.present? && !!uri_type(path)
end
- def tree?(path)
- repository.tree(current_sha, path).entries.any?
- end
+ def uri_type(path)
+ @uri_types[path] ||= begin
+ unescaped_path = Addressable::URI.unescape(path)
- def image?(path)
- repository.blob_at(current_sha, path).try(:image?)
+ current_commit.uri_type(unescaped_path)
+ end
end
- def current_sha
- context[:commit].try(:id) ||
- ref ? repository.commit(ref).try(:sha) : repository.head_commit.sha
+ def current_commit
+ @current_commit ||= context[:commit] ||
+ ref ? repository.commit(ref) : repository.head_commit
end
def relative_url_root
@@ -148,7 +125,7 @@ module Banzai
end
def repository
- context[:project].try(:repository)
+ @repository ||= context[:project].try(:repository)
end
end
end
diff --git a/lib/banzai/filter/syntax_highlight_filter.rb b/lib/banzai/filter/syntax_highlight_filter.rb
index 62a79c62e20..91f0159f9a1 100644
--- a/lib/banzai/filter/syntax_highlight_filter.rb
+++ b/lib/banzai/filter/syntax_highlight_filter.rb
@@ -19,24 +19,36 @@ module Banzai
language = node.attr('class')
code = node.text
+ css_classes = "code highlight"
+
+ lexer = Rouge::Lexer.find_fancy(language) || Rouge::Lexers::PlainText
+ formatter = Rouge::Formatters::HTML.new
+
begin
- highlighted = block_code(code, language)
+ code = formatter.format(lexer.lex(code))
+
+ css_classes << " js-syntax-highlight #{lexer.tag}"
rescue
# Gracefully handle syntax highlighter bugs/errors to ensure
# users can still access an issue/comment/etc.
- highlighted = "<pre>#{code}</pre>"
end
- # Replace the parent `pre` element with the entire highlighted block
- node.parent.replace(highlighted)
+ highlighted = %(<pre class="#{css_classes}"><code>#{code}</code></pre>)
+
+ # Extracted to a method to measure it
+ replace_parent_pre_element(node, highlighted)
end
private
+ def replace_parent_pre_element(node, highlighted)
+ # Replace the parent `pre` element with the entire highlighted block
+ node.parent.replace(highlighted)
+ end
+
# Override Rouge::Plugins::Redcarpet#rouge_formatter
def rouge_formatter(lexer)
- Rouge::Formatters::HTMLGitlab.new(
- cssclass: "code highlight js-syntax-highlight #{lexer.tag}")
+ Rouge::Formatters::HTML.new
end
end
end
diff --git a/lib/banzai/filter/user_reference_filter.rb b/lib/banzai/filter/user_reference_filter.rb
index 5b0a6d8541b..e1ca7f4d24b 100644
--- a/lib/banzai/filter/user_reference_filter.rb
+++ b/lib/banzai/filter/user_reference_filter.rb
@@ -112,7 +112,7 @@ module Banzai
data = data_attribute(project: project.id, author: author.try(:id))
text = link_text || User.reference_prefix + 'all'
- link_tag(url, data, text)
+ link_tag(url, data, text, 'All Project and Group Members')
end
def link_to_namespace(namespace, link_text: nil)
@@ -128,7 +128,7 @@ module Banzai
data = data_attribute(group: namespace.id)
text = link_text || Group.reference_prefix + group
- link_tag(url, data, text)
+ link_tag(url, data, text, namespace.name)
end
def link_to_user(user, namespace, link_text: nil)
@@ -136,11 +136,11 @@ module Banzai
data = data_attribute(user: namespace.owner_id)
text = link_text || User.reference_prefix + user
- link_tag(url, data, text)
+ link_tag(url, data, text, namespace.owner_name)
end
- def link_tag(url, data, text)
- %(<a href="#{url}" #{data} class="#{link_class}">#{escape_once(text)}</a>)
+ def link_tag(url, data, text, title)
+ %(<a href="#{url}" #{data} class="#{link_class}" title="#{escape_once(title)}">#{escape_once(text)}</a>)
end
end
end
diff --git a/lib/banzai/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/note_renderer.rb b/lib/banzai/note_renderer.rb
new file mode 100644
index 00000000000..bab6a9934d1
--- /dev/null
+++ b/lib/banzai/note_renderer.rb
@@ -0,0 +1,22 @@
+module Banzai
+ module NoteRenderer
+ # Renders a collection of Note instances.
+ #
+ # notes - The notes to render.
+ # project - The project to use for rendering/redacting.
+ # user - The user viewing the notes.
+ # path - The request path.
+ # wiki - The project's wiki.
+ # git_ref - The current Git reference.
+ def self.render(notes, project, user = nil, path = nil, wiki = nil, git_ref = nil)
+ renderer = ObjectRenderer.new(project,
+ user,
+ requested_path: path,
+ project_wiki: wiki,
+ ref: git_ref,
+ pipeline: :note)
+
+ renderer.render(notes, :note)
+ end
+ end
+end
diff --git a/lib/banzai/object_renderer.rb b/lib/banzai/object_renderer.rb
new file mode 100644
index 00000000000..9aef807c152
--- /dev/null
+++ b/lib/banzai/object_renderer.rb
@@ -0,0 +1,86 @@
+module Banzai
+ # Class for rendering multiple objects (e.g. Note instances) in a single pass.
+ #
+ # Rendered Markdown is stored in an attribute in every object based on the
+ # name of the attribute containing the Markdown. For example, when the
+ # attribute `note` is rendered the HTML is stored in `note_html`.
+ class ObjectRenderer
+ attr_reader :project, :user
+
+ # Make sure to set the appropriate pipeline in the `raw_context` attribute
+ # (e.g. `:note` for Note instances).
+ #
+ # project - A Project to use for rendering and redacting Markdown.
+ # user - The user viewing the Markdown/HTML documents, if any.
+ # context - A Hash containing extra attributes to use in the rendering
+ # pipeline.
+ def initialize(project, user = nil, raw_context = {})
+ @project = project
+ @user = user
+ @raw_context = raw_context
+ end
+
+ # Renders and redacts an Array of objects.
+ #
+ # objects - The objects to render
+ # attribute - The attribute containing the raw Markdown to render.
+ #
+ # Returns the same input objects.
+ def render(objects, attribute)
+ documents = render_objects(objects, attribute)
+ redacted = redact_documents(documents)
+
+ objects.each_with_index do |object, index|
+ redacted_data = redacted[index]
+ object.__send__("#{attribute}_html=", redacted_data[:document].to_html.html_safe)
+ object.user_visible_reference_count = redacted_data[:visible_reference_count]
+ end
+ end
+
+ # Renders the attribute of every given object.
+ def render_objects(objects, attribute)
+ render_attributes(objects, attribute)
+ end
+
+ # Redacts the list of documents.
+ #
+ # Returns an Array containing the redacted documents.
+ def redact_documents(documents)
+ redactor = Redactor.new(project, user)
+
+ redactor.redact(documents)
+ end
+
+ # Returns a Banzai context for the given object and attribute.
+ def context_for(object, attribute)
+ context = base_context.merge(cache_key: [object, attribute])
+
+ if object.respond_to?(:author)
+ context[:author] = object.author
+ end
+
+ context
+ end
+
+ # Renders the attributes of a set of objects.
+ #
+ # Returns an Array of `Nokogiri::HTML::Document`.
+ def render_attributes(objects, attribute)
+ strings_and_contexts = objects.map do |object|
+ context = context_for(object, attribute)
+
+ string = object.__send__(attribute)
+
+ { text: string, context: context }
+ end
+
+ Banzai.cache_collection_render(strings_and_contexts).each_with_index.map do |html, index|
+ Banzai::Pipeline[:relative_link].to_document(html, strings_and_contexts[index][:context])
+ end
+ end
+
+ def base_context
+ @base_context ||= @raw_context.merge(current_user: user, project: project)
+ end
+ end
+end
diff --git a/lib/banzai/pipeline/full_pipeline.rb b/lib/banzai/pipeline/full_pipeline.rb
index 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/banzai/pipeline/pre_process_pipeline.rb b/lib/banzai/pipeline/pre_process_pipeline.rb
index 50dc978b452..6cf219661d3 100644
--- a/lib/banzai/pipeline/pre_process_pipeline.rb
+++ b/lib/banzai/pipeline/pre_process_pipeline.rb
@@ -3,7 +3,8 @@ module Banzai
class PreProcessPipeline < BasePipeline
def self.filters
FilterArray[
- Filter::YamlFrontMatterFilter
+ Filter::YamlFrontMatterFilter,
+ Filter::BlockquoteFenceFilter,
]
end
diff --git a/lib/banzai/pipeline/relative_link_pipeline.rb b/lib/banzai/pipeline/relative_link_pipeline.rb
new file mode 100644
index 00000000000..270990e7ab4
--- /dev/null
+++ b/lib/banzai/pipeline/relative_link_pipeline.rb
@@ -0,0 +1,11 @@
+module Banzai
+ module Pipeline
+ class RelativeLinkPipeline < BasePipeline
+ def self.filters
+ FilterArray[
+ Filter::RelativeLinkFilter
+ ]
+ end
+ end
+ end
+end
diff --git a/lib/banzai/redactor.rb b/lib/banzai/redactor.rb
new file mode 100644
index 00000000000..0df3a72d1c4
--- /dev/null
+++ b/lib/banzai/redactor.rb
@@ -0,0 +1,82 @@
+module Banzai
+ # Class for removing Markdown references a certain user is not allowed to
+ # view.
+ class Redactor
+ attr_reader :user, :project
+
+ # project - A Project to use for redacting links.
+ # user - The currently logged in user (if any).
+ def initialize(project, user = nil)
+ @project = project
+ @user = user
+ end
+
+ # Redacts the references in the given Array of documents.
+ #
+ # This method modifies the given documents in-place.
+ #
+ # documents - A list of HTML documents containing references to redact.
+ #
+ # Returns the documents passed as the first argument.
+ def redact(documents)
+ all_document_nodes = document_nodes(documents)
+
+ redact_document_nodes(all_document_nodes)
+ end
+
+ # Redacts the given node documents
+ #
+ # data - An Array of a Hashes mapping an HTML document to nodes to redact.
+ def redact_document_nodes(all_document_nodes)
+ all_nodes = all_document_nodes.map { |x| x[:nodes] }.flatten
+ visible = nodes_visible_to_user(all_nodes)
+ metadata = []
+
+ all_document_nodes.each do |entry|
+ nodes_for_document = entry[:nodes]
+ doc_data = { document: entry[:document], visible_reference_count: nodes_for_document.count }
+ metadata << doc_data
+
+ nodes_for_document.each do |node|
+ next if visible.include?(node)
+
+ doc_data[:visible_reference_count] -= 1
+ # The reference should be replaced by the original text,
+ # which is not always the same as the rendered text.
+ text = node.attr('data-original') || node.text
+ node.replace(text)
+ end
+ end
+
+ metadata
+ end
+
+ # Returns the nodes visible to the current user.
+ #
+ # nodes - The input nodes to check.
+ #
+ # Returns a new Array containing the visible nodes.
+ def nodes_visible_to_user(nodes)
+ per_type = Hash.new { |h, k| h[k] = [] }
+ visible = Set.new
+
+ nodes.each do |node|
+ per_type[node.attr('data-reference-type')] << node
+ end
+
+ per_type.each do |type, nodes|
+ parser = Banzai::ReferenceParser[type].new(project, user)
+
+ visible.merge(parser.nodes_visible_to_user(user, nodes))
+ end
+
+ visible
+ end
+
+ def document_nodes(documents)
+ documents.map do |document|
+ { document: document, nodes: Querying.css(document, 'a.gfm[data-reference-type]') }
+ end
+ end
+ end
+end
diff --git a/lib/banzai/reference_parser/base_parser.rb b/lib/banzai/reference_parser/base_parser.rb
index 3d7b9c4a024..6cf218aaa0d 100644
--- a/lib/banzai/reference_parser/base_parser.rb
+++ b/lib/banzai/reference_parser/base_parser.rb
@@ -133,8 +133,9 @@ module Banzai
return {} if nodes.empty?
ids = unique_attribute_values(nodes, attribute)
+ rows = collection_objects_for_ids(collection, ids)
- collection.where(id: ids).each_with_object({}) do |row, hash|
+ rows.each_with_object({}) do |row, hash|
hash[row.id] = row
end
end
@@ -153,6 +154,31 @@ module Banzai
values.to_a
end
+ # Queries the collection for the objects with the given IDs.
+ #
+ # If the RequestStore module is enabled this method will only query any
+ # objects that have not yet been queried. For objects that have already
+ # been queried the object is returned from the cache.
+ def collection_objects_for_ids(collection, ids)
+ if RequestStore.active?
+ cache = collection_cache[collection_cache_key(collection)]
+ to_query = ids.map(&:to_i) - cache.keys
+
+ unless to_query.empty?
+ collection.where(id: to_query).each { |row| cache[row.id] = row }
+ end
+
+ cache.values
+ else
+ collection.where(id: ids)
+ end
+ end
+
+ # Returns the cache key to use for a collection.
+ def collection_cache_key(collection)
+ collection.respond_to?(:model) ? collection.model : collection
+ end
+
# Processes the list of HTML documents and returns an Array containing all
# the references.
def process(documents)
@@ -189,7 +215,7 @@ module Banzai
end
def find_projects_for_hash_keys(hash)
- Project.where(id: hash.keys)
+ collection_objects_for_ids(Project, hash.keys)
end
private
@@ -199,6 +225,12 @@ module Banzai
def lazy(&block)
Gitlab::Lazy.new(&block)
end
+
+ def collection_cache
+ RequestStore[:banzai_collection_cache] ||= Hash.new do |hash, key|
+ hash[key] = {}
+ end
+ end
end
end
end
diff --git a/lib/banzai/reference_parser/user_parser.rb b/lib/banzai/reference_parser/user_parser.rb
index a12b0d19560..863f5725d3b 100644
--- a/lib/banzai/reference_parser/user_parser.rb
+++ b/lib/banzai/reference_parser/user_parser.rb
@@ -73,7 +73,7 @@ module Banzai
def find_users(ids)
return [] if ids.empty?
- User.where(id: ids).to_a
+ collection_objects_for_ids(User, ids)
end
def find_users_for_groups(ids)
@@ -85,7 +85,8 @@ module Banzai
def find_users_for_projects(ids)
return [] if ids.empty?
- Project.where(id: ids).flat_map { |p| p.team.members.to_a }
+ collection_objects_for_ids(Project, ids).
+ flat_map { |p| p.team.members.to_a }
end
end
end
diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb
index c14a9c4c722..910687a7b6a 100644
--- a/lib/banzai/renderer.rb
+++ b/lib/banzai/renderer.rb
@@ -10,7 +10,7 @@ module Banzai
# requiring XHTML, such as Atom feeds, need to call `post_process` on the
# result, providing the appropriate `pipeline` option.
#
- # markdown - Markdown String
+ # text - Markdown String
# context - Hash of context options passed to our HTML Pipeline
#
# Returns an HTML-safe String
@@ -29,14 +29,62 @@ module Banzai
end
end
- def self.render_result(text, context = {})
- Pipeline[context[:pipeline]].call(text, context)
+ # Perform multiple render from an Array of Markdown String into an
+ # Array of HTML-safe String of HTML.
+ #
+ # As the rendered Markdown String can be already cached read all the data
+ # from the cache using Rails.cache.read_multi operation. If the Markdown String
+ # is not in the cache or it's not cacheable (no cache_key entry is provided in
+ # the context) the Markdown String is rendered and stored in the cache so the
+ # next render call gets the rendered HTML-safe String from the cache.
+ #
+ # For further explanation see #render method comments.
+ #
+ # texts_and_contexts - An Array of Hashes that contains the Markdown String (:text)
+ # an options passed to our HTML Pipeline (:context)
+ #
+ # If on the :context you specify a :cache_key entry will be used to retrieve it
+ # and cache the result of rendering the Markdown String.
+ #
+ # Returns an Array containing HTML-safe String instances.
+ #
+ # Example:
+ # texts_and_contexts
+ # => [{ text: '### Hello',
+ # context: { cache_key: [note, :note] } }]
+ def self.cache_collection_render(texts_and_contexts)
+ items_collection = texts_and_contexts.each_with_index do |item, index|
+ context = item[:context]
+ cache_key = full_cache_multi_key(context.delete(:cache_key), context[:pipeline])
+
+ item[:cache_key] = cache_key if cache_key
+ end
+
+ cacheable_items, non_cacheable_items = items_collection.partition { |item| item.key?(:cache_key) }
+
+ items_in_cache = []
+ items_not_in_cache = []
+
+ unless cacheable_items.empty?
+ items_in_cache = Rails.cache.read_multi(*cacheable_items.map { |item| item[:cache_key] })
+ items_not_in_cache = cacheable_items.reject do |item|
+ item[:rendered] = items_in_cache[item[:cache_key]]
+ items_in_cache.key?(item[:cache_key])
+ end
+ end
+
+ (items_not_in_cache + non_cacheable_items).each do |item|
+ item[:rendered] = render(item[:text], item[:context])
+ Rails.cache.write(item[:cache_key], item[:rendered]) if item[:cache_key]
+ end
+
+ items_collection.map { |item| item[:rendered] }
end
- def self.pre_process(text, context)
- pipeline = Pipeline[:pre_process]
+ def self.render_result(text, context = {})
+ text = Pipeline[:pre_process].to_html(text, context) if text
- pipeline.to_html(text, context)
+ Pipeline[context[:pipeline]].call(text, context)
end
# Perform post-processing on an HTML String
@@ -82,5 +130,13 @@ module Banzai
return unless cache_key
["banzai", *cache_key, pipeline_name || :full]
end
+
+ # To map Rails.cache.read_multi results we need to know the Rails.cache.expanded_key.
+ # Other option will be to generate stringified keys on our side and don't delegate to Rails.cache.expanded_key
+ # method.
+ def self.full_cache_multi_key(cache_key, pipeline_name)
+ return unless cache_key
+ Rails.cache.send(:expanded_key, full_cache_key(cache_key, pipeline_name))
+ end
end
end
diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb
index 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/api/runners.rb b/lib/ci/api/runners.rb
index 0c41f22c7c5..bcc82969eb3 100644
--- a/lib/ci/api/runners.rb
+++ b/lib/ci/api/runners.rb
@@ -28,12 +28,9 @@ module Ci
post "register" do
required_attributes! [:token]
- attributes = { description: params[:description],
- tag_list: params[:tag_list] }
-
- unless params[:run_untagged].nil?
- attributes[:run_untagged] = params[:run_untagged]
- end
+ attributes = attributes_for_keys(
+ [:description, :tag_list, :run_untagged, :locked]
+ )
runner =
if runner_registration_token_valid?
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 ed86de819eb..83afed9f49f 100644
--- a/lib/ci/gitlab_ci_yaml_processor.rb
+++ b/lib/ci/gitlab_ci_yaml_processor.rb
@@ -2,9 +2,8 @@ module Ci
class GitlabCiYamlProcessor
class ValidationError < StandardError; end
- include Gitlab::Ci::Config::Node::ValidationHelpers
+ include Gitlab::Ci::Config::Node::LegacyValidationHelpers
- DEFAULT_STAGES = %w(build test deploy)
DEFAULT_STAGE = 'test'
ALLOWED_YAML_KEYS = [:before_script, :after_script, :image, :services, :types, :stages, :variables, :cache]
ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services,
@@ -14,7 +13,7 @@ module Ci
ALLOWED_CACHE_KEYS = [:key, :untracked, :paths]
ALLOWED_ARTIFACTS_KEYS = [:name, :untracked, :paths, :when, :expire_in]
- attr_reader :after_script, :image, :services, :path, :cache
+ attr_reader :path, :cache, :stages
def initialize(config, path = nil)
@ci_config = Gitlab::Ci::Config.new(config)
@@ -22,79 +21,63 @@ module Ci
@path = path
- initial_parsing
+ unless @ci_config.valid?
+ raise ValidationError, @ci_config.errors.first
+ end
+ initial_parsing
validate!
rescue Gitlab::Ci::Config::Loader::FormatError => e
raise ValidationError, e.message
end
- def builds_for_stage_and_ref(stage, ref, tag = false, trigger_request = nil)
- builds.select do |build|
- build[:stage] == stage &&
- process?(build[:only], build[:except], ref, tag, trigger_request)
+ def jobs_for_ref(ref, tag = false, trigger_request = nil)
+ @jobs.select do |_, job|
+ process?(job[:only], job[:except], ref, tag, trigger_request)
end
end
- def builds
- @jobs.map do |name, job|
- build_job(name, job)
+ def jobs_for_stage_and_ref(stage, ref, tag = false, trigger_request = nil)
+ jobs_for_ref(ref, tag, trigger_request).select do |_, job|
+ job[:stage] == stage
end
end
- def stages
- @stages || DEFAULT_STAGES
- end
-
- def global_variables
- @variables
- end
-
- def job_variables(name)
- job = @jobs[name.to_sym]
- return [] unless job
-
- job[:variables] || []
+ def builds_for_ref(ref, tag = false, trigger_request = nil)
+ jobs_for_ref(ref, tag, trigger_request).map do |name, _|
+ build_attributes(name)
+ end
end
- private
-
- def initial_parsing
- @after_script = @config[:after_script]
- @image = @config[:image]
- @services = @config[:services]
- @stages = @config[:stages] || @config[:types]
- @variables = @config[:variables] || {}
- @cache = @config[:cache]
- @jobs = {}
-
- @config.except!(*ALLOWED_YAML_KEYS)
- @config.each { |name, param| add_job(name, param) }
-
- raise ValidationError, "Please define at least one job" if @jobs.none?
+ def builds_for_stage_and_ref(stage, ref, tag = false, trigger_request = nil)
+ jobs_for_stage_and_ref(stage, ref, tag, trigger_request).map do |name, _|
+ build_attributes(name)
+ end
end
- def add_job(name, job)
- return if name.to_s.start_with?('.')
-
- raise ValidationError, "Unknown parameter: #{name}" unless job.is_a?(Hash) && job.has_key?(:script)
-
- stage = job[:stage] || job[:type] || DEFAULT_STAGE
- @jobs[name] = { stage: stage }.merge(job)
+ def builds
+ @jobs.map do |name, _|
+ build_attributes(name)
+ end
end
- def build_job(name, job)
+ def build_attributes(name)
+ job = @jobs[name.to_sym] || {}
{
- 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],
- except: job[:except],
allow_failure: job[:allow_failure] || false,
when: job[:when] || 'on_success',
environment: job[:environment],
+ yaml_variables: yaml_variables(name),
options: {
image: job[:image] || @image,
services: job[:services] || @services,
@@ -106,62 +89,58 @@ module Ci
}
end
- def validate!
- unless @ci_config.valid?
- raise ValidationError, @ci_config.errors.first
- end
+ private
- validate_global!
+ def initial_parsing
+ @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.each do |name, job|
- validate_job!(name, job)
- end
+ @jobs = {}
- true
- end
+ @config.except!(*ALLOWED_YAML_KEYS)
+ @config.each { |name, param| add_job(name, param) }
- def validate_global!
- unless @after_script.nil? || validate_array_of_strings(@after_script)
- raise ValidationError, "after_script should be an array of strings"
- end
+ raise ValidationError, "Please define at least one job" if @jobs.none?
+ end
- unless @image.nil? || @image.is_a?(String)
- raise ValidationError, "image should be a string"
- end
+ def add_job(name, job)
+ return if name.to_s.start_with?('.')
- unless @services.nil? || validate_array_of_strings(@services)
- raise ValidationError, "services should be an array of strings"
- end
+ raise ValidationError, "Unknown parameter: #{name}" unless job.is_a?(Hash) && job.has_key?(:script)
- unless @stages.nil? || validate_array_of_strings(@stages)
- raise ValidationError, "stages should be an array of strings"
- end
+ stage = job[:stage] || job[:type] || DEFAULT_STAGE
+ @jobs[name] = { stage: stage }.merge(job)
+ end
- unless @variables.nil? || validate_variables(@variables)
- raise ValidationError, "variables should be a map of key-value strings"
+ def yaml_variables(name)
+ variables = global_variables.merge(job_variables(name))
+ variables.map do |key, value|
+ { key: key, value: value, public: true }
end
+ end
- validate_global_cache! if @cache
+ def global_variables
+ @variables || {}
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
+ def job_variables(name)
+ job = @jobs[name.to_sym]
+ return {} unless job
- if @cache[:key] && !validate_string(@cache[:key])
- raise ValidationError, "cache:key parameter should be a string"
- end
+ job[:variables] || {}
+ end
- if @cache[:untracked] && !validate_boolean(@cache[:untracked])
- raise ValidationError, "cache:untracked parameter should be an boolean"
+ def validate!
+ @jobs.each do |name, job|
+ validate_job!(name, job)
end
- if @cache[:paths] && !validate_array_of_strings(@cache[:paths])
- raise ValidationError, "cache:paths parameter should be an array of strings"
- end
+ true
end
def validate_job!(name, job)
@@ -216,8 +195,8 @@ module Ci
raise ValidationError, "#{name} job: allow_failure parameter should be an boolean"
end
- if job[:when] && !job[:when].in?(%w[on_success on_failure always])
- raise ValidationError, "#{name} job: when parameter should be on_success, on_failure or always"
+ if job[:when] && !job[:when].in?(%w[on_success on_failure always manual])
+ raise ValidationError, "#{name} job: when parameter should be on_success, on_failure, always or manual"
end
if job[:environment] && !validate_environment(job[:environment])
@@ -240,8 +219,8 @@ module Ci
end
def validate_job_stage!(name, job)
- unless job[:stage].is_a?(String) && job[:stage].in?(stages)
- raise ValidationError, "#{name} job: stage parameter should be #{stages.join(", ")}"
+ unless job[:stage].is_a?(String) && job[:stage].in?(@stages)
+ raise ValidationError, "#{name} job: stage parameter should be #{@stages.join(", ")}"
end
end
@@ -305,12 +284,12 @@ module Ci
raise ValidationError, "#{name} job: dependencies parameter should be an array of strings"
end
- stage_index = stages.index(job[:stage])
+ stage_index = @stages.index(job[:stage])
job[:dependencies].each do |dependency|
raise ValidationError, "#{name} job: undefined dependency: #{dependency}" unless @jobs[dependency.to_sym]
- unless stages.index(@jobs[dependency.to_sym][:stage]) < stage_index
+ unless @stages.index(@jobs[dependency.to_sym][:stage]) < stage_index
raise ValidationError, "#{name} job: dependency #{dependency} is not defined in prior stages"
end
end
diff --git a/lib/container_registry/client.rb b/lib/container_registry/client.rb
index 42232b7129d..2edddb84fc3 100644
--- a/lib/container_registry/client.rb
+++ b/lib/container_registry/client.rb
@@ -7,62 +7,91 @@ module ContainerRegistry
MANIFEST_VERSION = 'application/vnd.docker.distribution.manifest.v2+json'
+ # Taken from: FaradayMiddleware::FollowRedirects
+ REDIRECT_CODES = Set.new [301, 302, 303, 307]
+
def initialize(base_uri, options = {})
@base_uri = base_uri
- @faraday = Faraday.new(@base_uri) do |conn|
- initialize_connection(conn, options)
- end
+ @options = options
end
def repository_tags(name)
- response_body @faraday.get("/v2/#{name}/tags/list")
+ response_body faraday.get("/v2/#{name}/tags/list")
end
def repository_manifest(name, reference)
- response_body @faraday.get("/v2/#{name}/manifests/#{reference}")
+ response_body faraday.get("/v2/#{name}/manifests/#{reference}")
end
def repository_tag_digest(name, reference)
- response = @faraday.head("/v2/#{name}/manifests/#{reference}")
+ response = faraday.head("/v2/#{name}/manifests/#{reference}")
response.headers['docker-content-digest'] if response.success?
end
def delete_repository_tag(name, reference)
- @faraday.delete("/v2/#{name}/manifests/#{reference}").success?
+ faraday.delete("/v2/#{name}/manifests/#{reference}").success?
end
def blob(name, digest, type = nil)
- headers = {}
- headers['Accept'] = type if type
- response_body @faraday.get("/v2/#{name}/blobs/#{digest}", nil, headers)
+ type ||= 'application/octet-stream'
+ response_body faraday_blob.get("/v2/#{name}/blobs/#{digest}", nil, 'Accept' => type), allow_redirect: true
end
def delete_blob(name, digest)
- @faraday.delete("/v2/#{name}/blobs/#{digest}").success?
+ faraday.delete("/v2/#{name}/blobs/#{digest}").success?
end
-
+
private
-
+
def initialize_connection(conn, options)
conn.request :json
+
+ if options[:user] && options[:password]
+ conn.request(:basic_auth, options[:user].to_s, options[:password].to_s)
+ elsif options[:token]
+ conn.request(:authorization, :bearer, options[:token].to_s)
+ end
+
+ conn.adapter :net_http
+ end
+
+ def accept_manifest(conn)
conn.headers['Accept'] = MANIFEST_VERSION
conn.response :json, content_type: 'application/json'
conn.response :json, content_type: 'application/vnd.docker.distribution.manifest.v1+prettyjws'
conn.response :json, content_type: 'application/vnd.docker.distribution.manifest.v1+json'
conn.response :json, content_type: 'application/vnd.docker.distribution.manifest.v2+json'
+ end
- if options[:user] && options[:password]
- conn.request(:basic_auth, options[:user].to_s, options[:password].to_s)
- elsif options[:token]
- conn.request(:authorization, :bearer, options[:token].to_s)
+ def response_body(response, allow_redirect: false)
+ if allow_redirect && REDIRECT_CODES.include?(response.status)
+ response = redirect_response(response.headers['location'])
end
- conn.adapter :net_http
+ response.body if response && response.success?
+ end
+
+ def redirect_response(location)
+ return unless location
+
+ # We explicitly remove authorization token
+ faraday_blob.get(location) do |req|
+ req['Authorization'] = ''
+ end
end
- def response_body(response)
- response.body if response.success?
+ def faraday
+ @faraday ||= Faraday.new(@base_uri) do |conn|
+ initialize_connection(conn, @options)
+ accept_manifest(conn)
+ end
+ end
+
+ def faraday_blob
+ @faraday_blob ||= Faraday.new(@base_uri) do |conn|
+ initialize_connection(conn, @options)
+ end
end
end
end
diff --git a/lib/container_registry/tag.rb b/lib/container_registry/tag.rb
index 7a0929d774e..59040199920 100644
--- a/lib/container_registry/tag.rb
+++ b/lib/container_registry/tag.rb
@@ -3,6 +3,7 @@ module ContainerRegistry
attr_reader :repository, :name
delegate :registry, :client, to: :repository
+ delegate :revision, :short_revision, to: :config_blob, allow_nil: true
def initialize(repository, name)
@repository, @name = repository, name
@@ -52,7 +53,7 @@ module ContainerRegistry
def config
return unless config_blob
- @config ||= ContainerRegistry::Config.new(self, config_blob)
+ @config ||= ContainerRegistry::Config.new(self, config_blob) if config_blob.data
end
def created_at
diff --git a/lib/disable_email_interceptor.rb b/lib/disable_email_interceptor.rb
index 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/access.rb b/lib/gitlab/access.rb
index 6d0e30e916f..de41ea415a6 100644
--- a/lib/gitlab/access.rb
+++ b/lib/gitlab/access.rb
@@ -5,6 +5,8 @@
#
module Gitlab
module Access
+ class AccessDeniedError < StandardError; end
+
GUEST = 10
REPORTER = 20
DEVELOPER = 30
@@ -12,9 +14,10 @@ module Gitlab
OWNER = 50
# Branch protection settings
- PROTECTION_NONE = 0
- PROTECTION_DEV_CAN_PUSH = 1
- PROTECTION_FULL = 2
+ PROTECTION_NONE = 0
+ PROTECTION_DEV_CAN_PUSH = 1
+ PROTECTION_FULL = 2
+ PROTECTION_DEV_CAN_MERGE = 3
class << self
def values
@@ -52,6 +55,7 @@ module Gitlab
def protection_options
{
"Not protected: Both developers and masters can push new commits, force push, or delete the branch." => PROTECTION_NONE,
+ "Protected against pushes: Developers cannot push new commits, but are allowed to accept merge requests to the branch." => PROTECTION_DEV_CAN_MERGE,
"Partially protected: Developers can push new commits, but cannot force push or delete the branch. Masters can do all of those." => PROTECTION_DEV_CAN_PUSH,
"Fully protected: Developers cannot push new commits, force push, or delete the branch. Only masters can do any of those." => PROTECTION_FULL,
}
diff --git a/lib/gitlab/asciidoc.rb b/lib/gitlab/asciidoc.rb
index 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..39b43ab5489 100644
--- a/lib/gitlab/award_emoji.rb
+++ b/lib/gitlab/award_emoji.rb
@@ -1,24 +1,14 @@
module Gitlab
class AwardEmoji
CATEGORIES = {
- other: "Other",
objects: "Objects",
- places: "Places",
- travel_places: "Travel",
- emoticons: "Emoticons",
- objects_symbols: "Symbols",
+ travel: "Travel",
+ symbols: "Symbols",
nature: "Nature",
- celebration: "Celebration",
people: "People",
activity: "Activity",
flags: "Flags",
- food_drink: "Food"
- }.with_indifferent_access
-
- CATEGORY_ALIASES = {
- symbols: "objects_symbols",
- foods: "food_drink",
- travel: "travel_places"
+ food: "Food"
}.with_indifferent_access
def self.normalize_emoji_name(name)
@@ -35,7 +25,7 @@ module Gitlab
# Skip Fitzpatrick(tone) modifiers
next if data["category"] == "modifier"
- category = CATEGORY_ALIASES[data["category"]] || data["category"]
+ category = data["category"]
@emoji_by_category[category] << data
end
@@ -57,17 +47,26 @@ module Gitlab
def self.aliases
@aliases ||=
begin
- json_path = File.join(Rails.root, 'fixtures', 'emojis', 'aliases.json' )
- JSON.parse(File.read(json_path))
- end
+ json_path = File.join(Rails.root, 'fixtures', 'emojis', 'aliases.json')
+ JSON.parse(File.read(json_path))
+ end
end
# Returns an Array of Emoji names and their asset URLs.
def self.urls
@urls ||= begin
path = File.join(Rails.root, 'fixtures', 'emojis', 'digests.json')
+ # Construct the full asset path ourselves because
+ # ActionView::Helpers::AssetUrlHelper.asset_url is slow for hundreds
+ # of entries since it has to do a lot of extra work (e.g. regexps).
prefix = Gitlab::Application.config.assets.prefix
digest = Gitlab::Application.config.assets.digest
+ base =
+ if defined?(Gitlab::Application.config.relative_url_root) && Gitlab::Application.config.relative_url_root
+ Gitlab::Application.config.relative_url_root
+ else
+ ''
+ end
JSON.parse(File.read(path)).map do |hash|
if digest
@@ -76,7 +75,7 @@ module Gitlab
fname = hash['unicode']
end
- { name: hash['name'], path: "#{prefix}/#{fname}.png" }
+ { name: hash['name'], path: File.join(base, prefix, "#{fname}.png") }
end
end
end
diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb
index 7e3f5abba62..ab94abeda77 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
@@ -31,7 +30,7 @@ module Grack
auth!
- lfs_response = Gitlab::Lfs::Router.new(project, @user, @request).try_call
+ lfs_response = Gitlab::Lfs::Router.new(project, @user, @ci, @request).try_call
return lfs_response unless lfs_response.nil?
if @user.nil? && !@ci
@@ -64,7 +63,7 @@ module Grack
def ci_request?(login, password)
matched_login = /(?<s>^[a-zA-Z]*-ci)-token$/.match(login)
- if project && matched_login.present? && git_cmd == 'git-upload-pack'
+ if project && matched_login.present?
underscored_service = matched_login['s'].underscore
if underscored_service == 'gitlab_ci'
diff --git a/lib/gitlab/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/checks/change_access.rb b/lib/gitlab/checks/change_access.rb
new file mode 100644
index 00000000000..5551fac4b8b
--- /dev/null
+++ b/lib/gitlab/checks/change_access.rb
@@ -0,0 +1,96 @@
+module Gitlab
+ module Checks
+ class ChangeAccess
+ attr_reader :user_access, :project
+
+ def initialize(change, user_access:, project:)
+ @oldrev, @newrev, @ref = change.split(' ')
+ @branch_name = branch_name(@ref)
+ @user_access = user_access
+ @project = project
+ end
+
+ def exec
+ error = protected_branch_checks || tag_checks || push_checks
+
+ if error
+ GitAccessStatus.new(false, error)
+ else
+ GitAccessStatus.new(true)
+ end
+ end
+
+ protected
+
+ def protected_branch_checks
+ return unless project.protected_branch?(@branch_name)
+
+ if forced_push? && user_access.cannot_do_action?(:force_push_code_to_protected_branches)
+ return "You are not allowed to force push code to a protected branch on this project."
+ elsif Gitlab::Git.blank_ref?(@newrev) && user_access.cannot_do_action?(:remove_protected_branches)
+ return "You are not allowed to delete protected branches from this project."
+ end
+
+ if matching_merge_request?
+ if user_access.can_merge_to_branch?(@branch_name) || user_access.can_push_to_branch?(@branch_name)
+ return
+ else
+ "You are not allowed to merge code into protected branches on this project."
+ end
+ else
+ if user_access.can_push_to_branch?(@branch_name)
+ return
+ else
+ "You are not allowed to push code to protected branches on this project."
+ end
+ end
+ end
+
+ def tag_checks
+ tag_ref = tag_name(@ref)
+
+ if tag_ref && protected_tag?(tag_ref) && user_access.cannot_do_action?(:admin_project)
+ "You are not allowed to change existing tags on this project."
+ end
+ end
+
+ def push_checks
+ if user_access.cannot_do_action?(:push_code)
+ "You are not allowed to push code to this project."
+ end
+ end
+
+ private
+
+ def protected_tag?(tag_name)
+ project.repository.tag_exists?(tag_name)
+ end
+
+ def forced_push?
+ Gitlab::Checks::ForcePush.force_push?(@project, @oldrev, @newrev)
+ end
+
+ def matching_merge_request?
+ Checks::MatchingMergeRequest.new(@newrev, @branch_name, @project).match?
+ end
+
+ def branch_name(ref)
+ ref = @ref.to_s
+ if Gitlab::Git.branch_ref?(ref)
+ Gitlab::Git.ref_name(ref)
+ else
+ nil
+ end
+ end
+
+ def tag_name(ref)
+ ref = @ref.to_s
+ if Gitlab::Git.tag_ref?(ref)
+ Gitlab::Git.ref_name(ref)
+ else
+ nil
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/checks/force_push.rb b/lib/gitlab/checks/force_push.rb
new file mode 100644
index 00000000000..5fe86553bd0
--- /dev/null
+++ b/lib/gitlab/checks/force_push.rb
@@ -0,0 +1,17 @@
+module Gitlab
+ module Checks
+ class ForcePush
+ def self.force_push?(project, oldrev, newrev)
+ return false if project.empty_repo?
+
+ # Created or deleted branch
+ if Gitlab::Git.blank_ref?(oldrev) || Gitlab::Git.blank_ref?(newrev)
+ false
+ else
+ missed_ref, _ = Gitlab::Popen.popen(%W(#{Gitlab.config.git.bin_path} --git-dir=#{project.repository.path_to_repo} rev-list --max-count=1 #{oldrev} ^#{newrev}))
+ missed_ref.present?
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/checks/matching_merge_request.rb b/lib/gitlab/checks/matching_merge_request.rb
new file mode 100644
index 00000000000..849848515da
--- /dev/null
+++ b/lib/gitlab/checks/matching_merge_request.rb
@@ -0,0 +1,18 @@
+module Gitlab
+ module Checks
+ class MatchingMergeRequest
+ def initialize(newrev, branch_name, project)
+ @newrev = newrev
+ @branch_name = branch_name
+ @project = project
+ end
+
+ def match?
+ @project.merge_requests
+ .with_state(:locked)
+ .where(in_progress_merge_commit_sha: @newrev, target_branch: @branch_name)
+ .exists?
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb
index b48d3592f16..e6cc1529760 100644
--- a/lib/gitlab/ci/config.rb
+++ b/lib/gitlab/ci/config.rb
@@ -4,12 +4,11 @@ module Gitlab
# Base GitLab CI Configuration facade
#
class Config
- delegate :valid?, :errors, to: :@global
-
##
# Temporary delegations that should be removed after refactoring
#
- delegate :before_script, to: :@global
+ delegate :before_script, :image, :services, :after_script, :variables,
+ :stages, :cache, to: :@global
def initialize(config)
@config = Loader.new(config).load!
@@ -18,6 +17,14 @@ module Gitlab
@global.process!
end
+ def valid?
+ @global.valid?
+ end
+
+ def errors
+ @global.errors
+ end
+
def to_hash
@config
end
diff --git a/lib/gitlab/ci/config/node/boolean.rb b/lib/gitlab/ci/config/node/boolean.rb
new file mode 100644
index 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 d60f87f3f94..37936fc8242 100644
--- a/lib/gitlab/ci/config/node/configurable.rb
+++ b/lib/gitlab/ci/config/node/configurable.rb
@@ -15,43 +15,49 @@ module Gitlab
#
module Configurable
extend ActiveSupport::Concern
+ include Validatable
- def allowed_nodes
- self.class.allowed_nodes || {}
+ included do
+ validations do
+ validates :config, type: Hash
+ end
end
private
- def prevalidate!
- unless @value.is_a?(Hash)
- @errors << 'should be a configuration entry with hash value'
- end
- end
-
def create_node(key, factory)
- factory.with(value: @value[key])
- factory.nullify! unless @value.has_key?(key)
+ factory.with(value: @config[key], key: key, parent: self)
+
factory.create!
end
class_methods do
- def allowed_nodes
- Hash[@allowed_nodes.map { |key, factory| [key, factory.dup] }]
+ def nodes
+ Hash[(@nodes || {}).map { |key, factory| [key, factory.dup] }]
end
private
- def allow_node(symbol, entry_class, metadata)
+ def node(symbol, entry_class, metadata)
factory = Node::Factory.new(entry_class)
.with(description: metadata[:description])
- define_method(symbol) do
- raise Entry::InvalidError unless valid?
+ (@nodes ||= {}).merge!(symbol.to_sym => factory)
+ end
- @nodes[symbol].try(:value)
- end
+ def helpers(*nodes)
+ nodes.each do |symbol|
+ define_method("#{symbol}_defined?") do
+ @nodes[symbol].try(:defined?)
+ end
- (@allowed_nodes ||= {}).merge!(symbol => factory)
+ 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 52758a962f3..9e79e170a4f 100644
--- a/lib/gitlab/ci/config/node/entry.rb
+++ b/lib/gitlab/ci/config/node/entry.rb
@@ -8,14 +8,14 @@ module Gitlab
class Entry
class InvalidError < StandardError; end
- attr_accessor :description
+ attr_reader :config
+ attr_accessor :key, :parent, :description
- def initialize(value)
- @value = value
+ def initialize(config)
+ @config = config
@nodes = {}
- @errors = []
-
- prevalidate!
+ @validator = self.class.validator.new(self)
+ @validator.validate
end
def process!
@@ -23,50 +23,65 @@ module Gitlab
return unless valid?
compose!
-
- nodes.each(&:process!)
- nodes.each(&:validate!)
+ process_nodes!
end
def nodes
@nodes.values
end
- def valid?
- errors.none?
+ def leaf?
+ self.class.nodes.none?
end
- def leaf?
- allowed_nodes.none?
+ def ancestors
+ @parent ? @parent.ancestors + [@parent] : []
+ end
+
+ def valid?
+ errors.none?
end
def errors
- @errors + nodes.map(&:errors).flatten
+ @validator.messages + nodes.flat_map(&:errors)
end
- def allowed_nodes
- {}
+ def value
+ if leaf?
+ @config
+ else
+ defined = @nodes.select { |_key, value| value.defined? }
+ Hash[defined.map { |key, node| [key, node.value] }]
+ end
end
- def validate!
- raise NotImplementedError
+ def defined?
+ true
end
- def value
- raise NotImplementedError
+ def self.default
end
- private
+ def self.nodes
+ {}
+ end
- def prevalidate!
+ def self.validator
+ Validator
end
+ private
+
def compose!
- allowed_nodes.each do |key, essence|
+ self.class.nodes.each do |key, essence|
@nodes[key] = create_node(key, essence)
end
end
+ def process_nodes!
+ nodes.each(&:process!)
+ end
+
def create_node(key, essence)
raise NotImplementedError
end
diff --git a/lib/gitlab/ci/config/node/factory.rb b/lib/gitlab/ci/config/node/factory.rb
index 787ca006f5a..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,18 +18,29 @@ module Gitlab
self
end
- def nullify!
- @entry_class = Node::Null
- self
- end
-
def create!
raise InvalidFactory unless @attributes.has_key?(:value)
- @entry_class.new(@attributes[:value]).tap do |entry|
+ fabricate.tap do |entry|
+ entry.key = @attributes[:key]
+ entry.parent = @attributes[:parent]
entry.description = @attributes[:description]
end
end
+
+ private
+
+ def fabricate
+ ##
+ # We assume that unspecified entry is undefined.
+ # See issue #18775.
+ #
+ if @attributes[:value].nil?
+ Node::Undefined.new(@node)
+ else
+ @node.new(@attributes[:value])
+ end
+ end
end
end
end
diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb
index 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/validation_helpers.rb b/lib/gitlab/ci/config/node/legacy_validation_helpers.rb
index 72f648975dc..4d9a508796a 100644
--- a/lib/gitlab/ci/config/node/validation_helpers.rb
+++ b/lib/gitlab/ci/config/node/legacy_validation_helpers.rb
@@ -2,7 +2,7 @@ module Gitlab
module Ci
class Config
module Node
- module ValidationHelpers
+ module LegacyValidationHelpers
private
def validate_duration(value)
diff --git a/lib/gitlab/ci/config/node/null.rb b/lib/gitlab/ci/config/node/null.rb
deleted file mode 100644
index 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 5072bf0db7d..39328f0fade 100644
--- a/lib/gitlab/ci/config/node/script.rb
+++ b/lib/gitlab/ci/config/node/script.rb
@@ -5,22 +5,11 @@ module Gitlab
##
# Entry that represents a script.
#
- # Each element in the value array is a command that will be executed
- # by GitLab Runner. Currently we concatenate these commands with
- # new line character as a separator, what is compatible with
- # implementation in Runner.
- #
class Script < Entry
- include ValidationHelpers
-
- def value
- @value.join("\n")
- end
+ include Validatable
- def validate!
- unless validate_array_of_strings(@value)
- @errors << 'before_script should be an array of strings'
- end
+ validations do
+ validates :config, array_of_strings: true
end
end
end
diff --git a/lib/gitlab/ci/config/node/services.rb b/lib/gitlab/ci/config/node/services.rb
new file mode 100644
index 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/validatable.rb b/lib/gitlab/ci/config/node/validatable.rb
new file mode 100644
index 00000000000..f6e2896dfb2
--- /dev/null
+++ b/lib/gitlab/ci/config/node/validatable.rb
@@ -0,0 +1,29 @@
+module Gitlab
+ module Ci
+ class Config
+ module Node
+ module Validatable
+ extend ActiveSupport::Concern
+
+ class_methods do
+ def validator
+ validator = Class.new(Node::Validator)
+
+ if defined?(@validations)
+ @validations.each { |rules| validator.class_eval(&rules) }
+ end
+
+ validator
+ end
+
+ private
+
+ def validations(&block)
+ (@validations ||= []).append(block)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/node/validator.rb b/lib/gitlab/ci/config/node/validator.rb
new file mode 100644
index 00000000000..758a6cf4356
--- /dev/null
+++ b/lib/gitlab/ci/config/node/validator.rb
@@ -0,0 +1,41 @@
+module Gitlab
+ module Ci
+ class Config
+ module Node
+ class Validator < SimpleDelegator
+ include ActiveModel::Validations
+ include Node::Validators
+
+ def initialize(node)
+ super(node)
+ @node = node
+ end
+
+ def messages
+ errors.full_messages.map do |error|
+ "#{location} #{error}".downcase
+ end
+ end
+
+ def self.name
+ 'Validator'
+ end
+
+ def unknown_keys
+ return [] unless config.is_a?(Hash)
+
+ config.keys - @node.class.nodes.keys
+ end
+
+ private
+
+ def location
+ predecessors = ancestors.map(&:key).compact
+ current = key || @node.class.name.demodulize.underscore
+ predecessors.append(current).join(':')
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/node/validators.rb b/lib/gitlab/ci/config/node/validators.rb
new file mode 100644
index 00000000000..7b2f57990b5
--- /dev/null
+++ b/lib/gitlab/ci/config/node/validators.rb
@@ -0,0 +1,70 @@
+module Gitlab
+ module Ci
+ class Config
+ module Node
+ module Validators
+ class AllowedKeysValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ if record.unknown_keys.any?
+ unknown_list = record.unknown_keys.join(', ')
+ record.errors.add(:config,
+ "contains unknown keys: #{unknown_list}")
+ end
+ end
+ end
+
+ class ArrayOfStringsValidator < ActiveModel::EachValidator
+ include LegacyValidationHelpers
+
+ def validate_each(record, attribute, value)
+ unless validate_array_of_strings(value)
+ record.errors.add(attribute, 'should be an array of strings')
+ end
+ end
+ end
+
+ class BooleanValidator < ActiveModel::EachValidator
+ include LegacyValidationHelpers
+
+ def validate_each(record, attribute, value)
+ unless validate_boolean(value)
+ record.errors.add(attribute, 'should be a boolean value')
+ end
+ end
+ end
+
+ class KeyValidator < ActiveModel::EachValidator
+ include LegacyValidationHelpers
+
+ def validate_each(record, attribute, value)
+ unless validate_string(value)
+ record.errors.add(attribute, 'should be a string or symbol')
+ end
+ end
+ end
+
+ class TypeValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ type = options[:with]
+ raise unless type.is_a?(Class)
+
+ unless value.is_a?(type)
+ record.errors.add(attribute, "should be a #{type.name}")
+ end
+ end
+ end
+
+ class VariablesValidator < ActiveModel::EachValidator
+ include LegacyValidationHelpers
+
+ def validate_each(record, attribute, value)
+ unless validate_variables(value)
+ record.errors.add(attribute, 'should be a hash of key value pairs')
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/node/variables.rb b/lib/gitlab/ci/config/node/variables.rb
new file mode 100644
index 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/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index dec20d8659b..927f9dad20b 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -20,11 +20,19 @@ module Gitlab
if Database.postgresql?
options = options.merge({ algorithm: :concurrently })
+ disable_statement_timeout
end
add_index(table_name, column_name, options)
end
+ # Long-running migrations may take more than the timeout allowed by
+ # the database. Disable the session's statement timeout to ensure
+ # migrations don't get killed prematurely. (PostgreSQL only)
+ def disable_statement_timeout
+ ActiveRecord::Base.connection.execute('SET statement_timeout TO 0') if Database.postgresql?
+ end
+
# Updates the value of a column in batches.
#
# This method updates the table in batches of 5% of the total row count.
@@ -133,6 +141,8 @@ module Gitlab
'in the body of your migration class'
end
+ disable_statement_timeout
+
transaction do
add_column(table, column, type, default: nil)
diff --git a/lib/gitlab/diff/diff_refs.rb b/lib/gitlab/diff/diff_refs.rb
new file mode 100644
index 00000000000..8406ca4269c
--- /dev/null
+++ b/lib/gitlab/diff/diff_refs.rb
@@ -0,0 +1,36 @@
+module Gitlab
+ module Diff
+ class DiffRefs
+ attr_reader :base_sha
+ attr_reader :start_sha
+ attr_reader :head_sha
+
+ def initialize(base_sha:, start_sha: base_sha, head_sha:)
+ @base_sha = base_sha
+ @start_sha = start_sha
+ @head_sha = head_sha
+ end
+
+ def ==(other)
+ other.is_a?(self.class) &&
+ base_sha == other.base_sha &&
+ start_sha == other.start_sha &&
+ head_sha == other.head_sha
+ end
+
+ # There is only one case in which we will have `start_sha` and `head_sha`,
+ # but not `base_sha`, which is when a diff is generated between an
+ # orphaned branch and another branch, which means there _is_ no base, but
+ # we're still able to highlight it, and to create diff notes, which are
+ # the primary things `DiffRefs` are used for.
+ # `DiffRefs` are "complete" when they have `start_sha` and `head_sha`,
+ # because `base_sha` can always be derived from this, to return an actual
+ # sha, or `nil`.
+ # We have `base_sha` directly available on `DiffRefs` because it's faster#
+ # than having to look it up in the repo every time.
+ def complete?
+ start_sha && head_sha
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb
index d2e85cabf72..b09ca1fb8b0 100644
--- a/lib/gitlab/diff/file.rb
+++ b/lib/gitlab/diff/file.rb
@@ -1,47 +1,83 @@
module Gitlab
module Diff
class File
- attr_reader :diff, :diff_refs
+ attr_reader :diff, :repository, :diff_refs
delegate :new_file, :deleted_file, :renamed_file,
- :old_path, :new_path, to: :diff, prefix: false
+ :old_path, :new_path, :a_mode, :b_mode,
+ :submodule?, :too_large?, :collapsed?, to: :diff, prefix: false
- def initialize(diff, diff_refs)
+ def initialize(diff, repository:, diff_refs: nil)
@diff = diff
+ @repository = repository
@diff_refs = diff_refs
end
+ def position(line)
+ return unless diff_refs
+
+ Position.new(
+ old_path: old_path,
+ new_path: new_path,
+ old_line: line.old_line,
+ new_line: line.new_line,
+ diff_refs: diff_refs
+ )
+ end
+
+ def line_code(line)
+ return if line.meta?
+
+ Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos)
+ end
+
+ def line_for_line_code(code)
+ diff_lines.find { |line| line_code(line) == code }
+ end
+
+ def line_for_position(pos)
+ diff_lines.find { |line| position(line) == pos }
+ end
+
+ def position_for_line_code(code)
+ line = line_for_line_code(code)
+ position(line) if line
+ end
+
+ def line_code_for_position(pos)
+ line = line_for_position(pos)
+ line_code(line) if line
+ end
+
+ def content_commit
+ return unless diff_refs
+
+ repository.commit(deleted_file ? old_ref : new_ref)
+ end
+
def old_ref
- diff_refs[0] if diff_refs
+ diff_refs.try(:base_sha)
end
def new_ref
- diff_refs[1] if diff_refs
+ diff_refs.try(:head_sha)
end
- # Array of Gitlab::DIff::Line objects
+ # Array of Gitlab::Diff::Line objects
def diff_lines
- @lines ||= parser.parse(raw_diff.each_line).to_a
- end
-
- def too_large?
- diff.too_large?
+ @lines ||= Gitlab::Diff::Parser.new.parse(raw_diff.each_line).to_a
end
def highlighted_diff_lines
- Gitlab::Diff::Highlight.new(self).highlight
+ @highlighted_diff_lines ||= Gitlab::Diff::Highlight.new(self, repository: self.repository).highlight
end
def parallel_diff_lines
- Gitlab::Diff::ParallelDiff.new(self).parallelize
+ @parallel_diff_lines ||= Gitlab::Diff::ParallelDiff.new(self).parallelize
end
def mode_changed?
- !!(diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode)
- end
-
- def parser
- Gitlab::Diff::Parser.new
+ a_mode && b_mode && a_mode != b_mode
end
def raw_diff
@@ -53,17 +89,15 @@ module Gitlab
end
def prev_line(index)
- if index > 0
- diff_lines[index - 1]
- end
+ diff_lines[index - 1] if index > 0
+ end
+
+ def paths
+ [old_path, new_path].compact
end
def file_path
- if diff.new_path.present?
- diff.new_path
- elsif diff.old_path.present?
- diff.old_path
- end
+ new_path.presence || old_path
end
def added_lines
@@ -73,6 +107,21 @@ module Gitlab
def removed_lines
diff_lines.count(&:removed?)
end
+
+ def old_blob(commit = content_commit)
+ return unless commit
+
+ parent_id = commit.parent_id
+ return unless parent_id
+
+ repository.blob_at(parent_id, old_path)
+ end
+
+ def blob(commit = content_commit)
+ return unless commit
+
+ repository.blob_at(commit.id, file_path)
+ end
end
end
end
diff --git a/lib/gitlab/diff/highlight.rb b/lib/gitlab/diff/highlight.rb
index 9429b3ff88d..649a265a02c 100644
--- a/lib/gitlab/diff/highlight.rb
+++ b/lib/gitlab/diff/highlight.rb
@@ -1,11 +1,13 @@
module Gitlab
module Diff
class Highlight
- attr_reader :diff_file, :diff_lines, :raw_lines
+ attr_reader :diff_file, :diff_lines, :raw_lines, :repository
delegate :old_path, :new_path, :old_ref, :new_ref, to: :diff_file, prefix: :diff
- def initialize(diff_lines)
+ def initialize(diff_lines, repository: nil)
+ @repository = repository
+
if diff_lines.is_a?(Gitlab::Diff::File)
@diff_file = diff_lines
@diff_lines = @diff_file.diff_lines
@@ -19,7 +21,7 @@ module Gitlab
@diff_lines.map.with_index do |diff_line, i|
diff_line = diff_line.dup
# ignore highlighting for "match" lines
- next diff_line if diff_line.type == 'match' || diff_line.type == 'nonewline'
+ next diff_line if diff_line.meta?
rich_line = highlight_line(diff_line) || diff_line.text
@@ -40,12 +42,12 @@ module Gitlab
line_prefix = diff_line.text.match(/\A(.)/) ? $1 : ' '
- case diff_line.type
- when 'new', nil
- rich_line = new_lines[diff_line.new_pos - 1]
- when 'old'
- rich_line = old_lines[diff_line.old_pos - 1]
- end
+ rich_line =
+ if diff_line.unchanged? || diff_line.added?
+ new_lines[diff_line.new_pos - 1]
+ elsif diff_line.removed?
+ old_lines[diff_line.old_pos - 1]
+ end
# Only update text if line is found. This will prevent
# issues with submodules given the line only exists in diff content.
@@ -58,19 +60,12 @@ module Gitlab
def old_lines
return unless diff_file
- @old_lines ||= Gitlab::Highlight.highlight_lines(*processing_args(:old))
+ @old_lines ||= Gitlab::Highlight.highlight_lines(self.repository, diff_old_ref, diff_old_path)
end
def new_lines
return unless diff_file
- @new_lines ||= Gitlab::Highlight.highlight_lines(*processing_args(:new))
- end
-
- def processing_args(version)
- ref = send("diff_#{version}_ref")
- path = send("diff_#{version}_path")
-
- [ref.project.repository, ref.id, path]
+ @new_lines ||= Gitlab::Highlight.highlight_lines(self.repository, diff_new_ref, diff_new_path)
end
end
end
diff --git a/lib/gitlab/diff/inline_diff.rb b/lib/gitlab/diff/inline_diff.rb
index 789c14518b0..28ad637fda4 100644
--- a/lib/gitlab/diff/inline_diff.rb
+++ b/lib/gitlab/diff/inline_diff.rb
@@ -1,16 +1,30 @@
module Gitlab
module Diff
class InlineDiff
+ # Regex to find a run of deleted lines followed by the same number of added lines
+ LINE_PAIRS_PATTERN = %r{
+ # Runs start at the beginning of the string (the first line) or after a space (for an unchanged line)
+ (?:\A|\s)
+
+ # This matches a number of `-`s followed by the same number of `+`s through recursion
+ (?<del_ins>
+ -
+ \g<del_ins>?
+ \+
+ )
+
+ # Runs end at the end of the string (the last line) or before a space (for an unchanged line)
+ (?=\s|\z)
+ }x.freeze
+
attr_accessor :old_line, :new_line, :offset
def self.for_lines(lines)
- local_edit_indexes = self.find_local_edits(lines)
+ changed_line_pairs = self.find_changed_line_pairs(lines)
inline_diffs = []
- local_edit_indexes.each do |index|
- old_index = index
- new_index = index + 1
+ changed_line_pairs.each do |old_index, new_index|
old_line = lines[old_index]
new_line = lines[new_index]
@@ -51,18 +65,28 @@ module Gitlab
private
- def self.find_local_edits(lines)
- line_prefixes = lines.map { |line| line.match(/\A([+-])/) ? $1 : ' ' }
- joined_line_prefixes = " #{line_prefixes.join} "
-
- offset = 0
- local_edit_indexes = []
- while index = joined_line_prefixes.index(" -+ ", offset)
- local_edit_indexes << index
- offset = index + 1
+ # Finds pairs of old/new line pairs that represent the same line that changed
+ def self.find_changed_line_pairs(lines)
+ # Prefixes of all diff lines, indicating their types
+ # For example: `" - + -+ ---+++ --+ -++"`
+ line_prefixes = lines.each_with_object("") { |line, s| s << line[0] }.gsub(/[^ +-]/, ' ')
+
+ changed_line_pairs = []
+ line_prefixes.scan(LINE_PAIRS_PATTERN) do
+ # For `"---+++"`, `begin_index == 0`, `end_index == 6`
+ begin_index, end_index = Regexp.last_match.offset(:del_ins)
+
+ # For `"---+++"`, `changed_line_count == 3`
+ changed_line_count = (end_index - begin_index) / 2
+
+ halfway_index = begin_index + changed_line_count
+ (begin_index...halfway_index).each do |i|
+ # For `"---+++"`, index 1 maps to 1 + 3 = 4
+ changed_line_pairs << [i, i + changed_line_count]
+ end
end
- local_edit_indexes
+ changed_line_pairs
end
def longest_common_prefix(a, b)
diff --git a/lib/gitlab/diff/line.rb b/lib/gitlab/diff/line.rb
index 03730b435ad..c6189d660c2 100644
--- a/lib/gitlab/diff/line.rb
+++ b/lib/gitlab/diff/line.rb
@@ -9,6 +9,18 @@ module Gitlab
@old_pos, @new_pos = old_pos, new_pos
end
+ def old_line
+ old_pos unless added? || meta?
+ end
+
+ def new_line
+ new_pos unless removed? || meta?
+ end
+
+ def unchanged?
+ type.nil?
+ end
+
def added?
type == 'new'
end
@@ -16,6 +28,10 @@ module Gitlab
def removed?
type == 'old'
end
+
+ def meta?
+ type == 'match' || type == 'nonewline'
+ end
end
end
end
diff --git a/lib/gitlab/diff/line_mapper.rb b/lib/gitlab/diff/line_mapper.rb
new file mode 100644
index 00000000000..576a761423e
--- /dev/null
+++ b/lib/gitlab/diff/line_mapper.rb
@@ -0,0 +1,64 @@
+# When provided a diff for a specific file, maps old line numbers to new line
+# numbers and back, to find out where a specific line in a file was moved by the
+# changes.
+module Gitlab
+ module Diff
+ class LineMapper
+ attr_accessor :diff_file
+
+ def initialize(diff_file)
+ @diff_file = diff_file
+ end
+
+ # Find new line number for old line number.
+ def old_to_new(old_line)
+ map_line_number(old_line, from: :old_line, to: :new_line)
+ end
+
+ # Find old line number for new line number.
+ def new_to_old(new_line)
+ map_line_number(new_line, from: :new_line, to: :old_line)
+ end
+
+ private
+
+ def diff_lines
+ @diff_lines ||= @diff_file.diff_lines
+ end
+
+ # Find old/new line number based on its old/new counterpart line number.
+ def map_line_number(from_line, from:, to:)
+ # If no diff file could be found, the file wasn't changed, and the
+ # mapped line number is the same as the specified line number.
+ return from_line unless diff_file
+
+ # To find the mapped line number for the specified line number,
+ # we need to find:
+ # - The diff line with that exact line number, if it is in the diff context
+ # - The first diff line with a higher line number, if it falls between diff contexts
+ # - The last known diff line, if it falls after the last diff context
+ diff_line = diff_lines.find do |diff_line|
+ diff_from_line = diff_line.send(from)
+ diff_from_line && diff_from_line >= from_line
+ end
+ diff_line ||= diff_lines.last
+
+ # If no diff line could be found, the file wasn't changed, and the
+ # mapped line number is the same as the specified line number.
+ return from_line unless diff_line
+
+ diff_from_line = diff_line.send(from)
+ diff_to_line = diff_line.send(to)
+
+ # If the line was removed, there is no mapped line number.
+ return unless diff_to_line
+
+ # Because we may not have the diff line with the exact line number
+ # we were looking for, we need to adjust the mapped line number.
+ distance = diff_from_line - from_line
+
+ diff_to_line - distance
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/diff/parallel_diff.rb b/lib/gitlab/diff/parallel_diff.rb
index 74f9b3c050a..b069afdd28c 100644
--- a/lib/gitlab/diff/parallel_diff.rb
+++ b/lib/gitlab/diff/parallel_diff.rb
@@ -8,111 +8,96 @@ module Gitlab
end
def parallelize
- lines = []
- skip_next = false
+ i = 0
+ free_right_index = nil
+
+ lines = []
highlighted_diff_lines = diff_file.highlighted_diff_lines
highlighted_diff_lines.each do |line|
- full_line = line.text
- type = line.type
- line_code = generate_line_code(diff_file.file_path, line)
- line_new = line.new_pos
- line_old = line.old_pos
+ line_code = diff_file.line_code(line)
+ position = diff_file.position(line)
- next_line = diff_file.next_line(line.index)
-
- if next_line
- next_line = highlighted_diff_lines[next_line.index]
- next_line_code = generate_line_code(diff_file.file_path, next_line)
- next_type = next_line.type
- next_line = next_line.text
- end
-
- case type
+ case line.type
when 'match', nil
# line in the right panel is the same as in the left one
lines << {
left: {
- type: type,
- number: line_old,
- text: full_line,
+ type: line.type,
+ number: line.old_pos,
+ text: line.text,
line_code: line_code,
+ position: position
},
right: {
- type: type,
- number: line_new,
- text: full_line,
- line_code: line_code
+ type: line.type,
+ number: line.new_pos,
+ text: line.text,
+ line_code: line_code,
+ position: position
}
}
+
+ free_right_index = nil
+ i += 1
when 'old'
- case next_type
- when 'new'
- # Left side has text removed, right side has text added
- lines << {
- left: {
- type: type,
- number: line_old,
- text: full_line,
- line_code: line_code,
- },
- right: {
- type: next_type,
- number: line_new,
- text: next_line,
- line_code: next_line_code
- }
- }
- skip_next = true
- when 'old', 'nonewline', nil
- # Left side has text removed, right side doesn't have any change
- # No next line code, no new line number, no new line text
- lines << {
- left: {
- type: type,
- number: line_old,
- text: full_line,
- line_code: line_code,
- },
- right: {
- type: next_type,
- number: nil,
- text: "",
- line_code: nil
- }
+ lines << {
+ left: {
+ type: line.type,
+ number: line.old_pos,
+ text: line.text,
+ line_code: line_code,
+ position: position
+ },
+ right: {
+ type: nil,
+ number: nil,
+ text: "",
+ line_code: line_code,
+ position: position
}
- end
+ }
+
+ # Once we come upon a new line it can be put on the right of this old line
+ free_right_index ||= i
+ i += 1
when 'new'
- if skip_next
- # Change has been already included in previous line so no need to do it again
- skip_next = false
- next
+ data = {
+ type: line.type,
+ number: line.new_pos,
+ text: line.text,
+ line_code: line_code,
+ position: position
+ }
+
+ if free_right_index
+ # If an old line came before this without a line on the right, this
+ # line can be put to the right of it.
+ lines[free_right_index][:right] = data
+
+ # If there are any other old lines on the left that don't yet have
+ # a new counterpart on the right, update the free_right_index
+ next_free_right_index = free_right_index + 1
+ free_right_index = next_free_right_index < i ? next_free_right_index : nil
else
- # Change is only on the right side, left side has no change
lines << {
left: {
type: nil,
number: nil,
text: "",
line_code: line_code,
+ position: position
},
- right: {
- type: type,
- number: line_new,
- text: full_line,
- line_code: line_code
- }
+ right: data
}
+
+ free_right_index = nil
+ i += 1
end
end
end
- lines
- end
- private
-
- def generate_line_code(file_path, line)
- Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos)
+ lines
end
end
end
diff --git a/lib/gitlab/diff/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 e2fee6b9f3e..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, 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/force_push_check.rb b/lib/gitlab/force_push_check.rb
deleted file mode 100644
index 93c6a5bb7f5..00000000000
--- a/lib/gitlab/force_push_check.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-module Gitlab
- class ForcePushCheck
- def self.force_push?(project, oldrev, newrev)
- return false if project.empty_repo?
-
- # Created or deleted branch
- if Gitlab::Git.blank_ref?(oldrev) || Gitlab::Git.blank_ref?(newrev)
- false
- else
- missed_refs, _ = Gitlab::Popen.popen(%W(#{Gitlab.config.git.bin_path} --git-dir=#{project.repository.path_to_repo} rev-list #{oldrev} ^#{newrev}))
- missed_refs.split("\n").size > 0
- end
- end
- end
-end
diff --git a/lib/gitlab/git/hook.rb b/lib/gitlab/git/hook.rb
index 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..8e8f39d9cb2 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -1,63 +1,31 @@
+# Check a user's access to perform a git action. All public methods in this
+# class return an instance of `GitlabAccessStatus`
module Gitlab
class GitAccess
DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive }
PUSH_COMMANDS = %w{ git-receive-pack }
- attr_reader :actor, :project
+ attr_reader :actor, :project, :protocol, :user_access
- def initialize(actor, project)
+ def initialize(actor, project, protocol)
@actor = actor
@project = project
- end
-
- def user
- return @user if defined?(@user)
-
- @user =
- case actor
- when User
- actor
- when DeployKey
- nil
- when Key
- actor.user
- end
- end
-
- def deploy_key
- actor if actor.is_a?(DeployKey)
- end
-
- def can_push_to_branch?(ref)
- return false unless user
-
- if project.protected_branch?(ref) && !project.developers_can_push_to_protected_branch?(ref)
- user.can?(:push_code_to_protected_branches, project)
- else
- user.can?(:push_code, project)
- end
- end
-
- def can_read_project?
- if user
- user.can?(:read_project, project)
- elsif deploy_key
- deploy_key.projects.include?(project)
- else
- false
- end
+ @protocol = protocol
+ @user_access = UserAccess.new(user, project: project)
end
def check(cmd, changes = nil)
+ return build_status_object(false, "Git access over #{protocol.upcase} is not allowed") unless protocol_allowed?
+
unless actor
return build_status_object(false, "No user or key was provided.")
end
- if user && !user_allowed?
+ if user && !user_access.allowed?
return build_status_object(false, "Your account has been blocked.")
end
- unless project && can_read_project?
+ unless project && (user_access.can_read_project? || deploy_key_can_read_project?)
return build_status_object(false, 'The project you were looking for could not be found.')
end
@@ -92,7 +60,7 @@ module Gitlab
end
def user_download_access_check
- unless user.can?(:download_code, project)
+ unless user_access.can_do_action?(:download_code)
return build_status_object(false, "You are not allowed to download code from this project.")
end
@@ -122,92 +90,49 @@ module Gitlab
build_status_object(true)
end
- def can_user_do_action?(action)
- @permission_cache ||= {}
- @permission_cache[action] ||= user.can?(action, project)
- end
-
def change_access_check(change)
- oldrev, newrev, ref = change.split(' ')
-
- action =
- if project.protected_branch?(branch_name(ref))
- protected_branch_action(oldrev, newrev, branch_name(ref))
- elsif (tag_ref = tag_name(ref)) && protected_tag?(tag_ref)
- # Prevent any changes to existing git tag unless user has permissions
- :admin_project
- else
- :push_code
- end
-
- unless can_user_do_action?(action)
- status =
- case action
- when :force_push_code_to_protected_branches
- build_status_object(false, "You are not allowed to force push code to a protected branch on this project.")
- when :remove_protected_branches
- build_status_object(false, "You are not allowed to deleted protected branches from this project.")
- when :push_code_to_protected_branches
- build_status_object(false, "You are not allowed to push code to protected branches on this project.")
- when :admin_project
- build_status_object(false, "You are not allowed to change existing tags on this project.")
- else # :push_code
- build_status_object(false, "You are not allowed to push code to this project.")
- end
- return status
- end
-
- build_status_object(true)
+ Checks::ChangeAccess.new(change, user_access: user_access, project: project).exec
end
- def forced_push?(oldrev, newrev)
- Gitlab::ForcePushCheck.force_push?(project, oldrev, newrev)
+ def protocol_allowed?
+ Gitlab::ProtocolAccess.allowed?(protocol)
end
private
- def protected_branch_action(oldrev, newrev, branch_name)
- # we dont allow force push to protected branch
- if forced_push?(oldrev, newrev)
- :force_push_code_to_protected_branches
- elsif Gitlab::Git.blank_ref?(newrev)
- # and we dont allow remove of protected branch
- :remove_protected_branches
- elsif project.developers_can_push_to_protected_branch?(branch_name)
- :push_code
- else
- :push_code_to_protected_branches
- end
- end
-
- def protected_tag?(tag_name)
- project.repository.tag_exists?(tag_name)
- end
-
- def user_allowed?
- Gitlab::UserAccess.allowed?(user)
+ def matching_merge_request?(newrev, branch_name)
+ Checks::MatchingMergeRequest.new(newrev, branch_name, project).match?
end
- def branch_name(ref)
- ref = ref.to_s
- if Gitlab::Git.branch_ref?(ref)
- Gitlab::Git.ref_name(ref)
- else
- nil
- end
+ def deploy_key
+ actor if actor.is_a?(DeployKey)
end
- def tag_name(ref)
- ref = ref.to_s
- if Gitlab::Git.tag_ref?(ref)
- Gitlab::Git.ref_name(ref)
+ def deploy_key_can_read_project?
+ if deploy_key
+ return true if project.public?
+ deploy_key.projects.include?(project)
else
- nil
+ false
end
end
protected
+ def user
+ return @user if defined?(@user)
+
+ @user =
+ case actor
+ when User
+ actor
+ when DeployKey
+ nil
+ when Key
+ actor.user
+ end
+ end
+
def build_status_object(status, message = '')
GitAccessStatus.new(status, message)
end
diff --git a/lib/gitlab/git_access_wiki.rb b/lib/gitlab/git_access_wiki.rb
index 8672cbc0ec4..f71d3575909 100644
--- a/lib/gitlab/git_access_wiki.rb
+++ b/lib/gitlab/git_access_wiki.rb
@@ -1,7 +1,7 @@
module Gitlab
class GitAccessWiki < GitAccess
def change_access_check(change)
- if user.can?(:create_wiki, project)
+ if user_access.can_do_action?(:create_wiki)
build_status_object(true)
else
build_status_object(false, "You are not allowed to write to this project's wiki.")
diff --git a/lib/gitlab/github_import/branch_formatter.rb b/lib/gitlab/github_import/branch_formatter.rb
index 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..084e514492c 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
- @api = ::Octokit::Client.new(
- access_token: access_token,
- api_endpoint: github_options[:site],
- connection_options: {
- ssl: { verify: config['verify_ssl'] }
- }
- )
+ def api
+ @api ||= ::Octokit::Client.new(
+ access_token: access_token,
+ api_endpoint: github_options[:site],
+ # If there is no config, we're connecting to github.com and we
+ # should verify ssl.
+ connection_options: {
+ ssl: { verify: config ? config['verify_ssl'] : true }
+ }
+ )
+ end
+
+ def client
+ unless config
+ raise Projects::ImportService::Error,
+ 'OAuth configuration for GitHub missing.'
end
+
+ @client ||= ::OAuth2::Client.new(
+ config.app_id,
+ config.app_secret,
+ github_options.merge(ssl: { verify: config['verify_ssl'] })
+ )
end
def authorize_url(redirect_uri)
@@ -56,15 +69,30 @@ module Gitlab
end
def github_options
- config["args"]["client_options"].deep_symbolize_keys
+ if config
+ config["args"]["client_options"].deep_symbolize_keys
+ else
+ OmniAuth::Strategies::GitHub.default_options[:client_options].symbolize_keys
+ end
end
def rate_limit
api.rate_limit!
+ # GitHub Rate Limit API returns 404 when the rate limit is
+ # disabled. In this case we just want to return gracefully
+ # instead of spitting out an error.
+ rescue Octokit::NotFound
+ nil
+ end
+
+ def has_rate_limit?
+ return @has_rate_limit if defined?(@has_rate_limit)
+
+ @has_rate_limit = rate_limit.present?
end
def rate_limit_exceed?
- rate_limit.remaining <= GITHUB_SAFE_REMAINING_REQUESTS
+ has_rate_limit? && rate_limit.remaining <= GITHUB_SAFE_REMAINING_REQUESTS
end
def rate_limit_sleep_time
diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb
index 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/gitignore.rb b/lib/gitlab/gitignore.rb
deleted file mode 100644
index f46b43b61a4..00000000000
--- a/lib/gitlab/gitignore.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-module Gitlab
- class Gitignore
- FILTER_REGEX = /\.gitignore\z/.freeze
-
- def initialize(path)
- @path = path
- end
-
- def name
- File.basename(@path, '.gitignore')
- end
-
- def content
- File.read(@path)
- end
-
- class << self
- def all
- languages_frameworks + global
- end
-
- def find(key)
- file_name = "#{key}.gitignore"
-
- directory = select_directory(file_name)
- directory ? new(File.join(directory, file_name)) : nil
- end
-
- def global
- files_for_folder(global_dir).map { |file| new(File.join(global_dir, file)) }
- end
-
- def languages_frameworks
- files_for_folder(gitignore_dir).map { |file| new(File.join(gitignore_dir, file)) }
- end
-
- private
-
- def select_directory(file_name)
- [gitignore_dir, global_dir].find { |dir| File.exist?(File.join(dir, file_name)) }
- end
-
- def global_dir
- File.join(gitignore_dir, 'Global')
- end
-
- def gitignore_dir
- Rails.root.join('vendor/gitignore')
- end
-
- def files_for_folder(dir)
- Dir.glob("#{dir.to_s}/*.gitignore").map { |file| file.gsub(FILTER_REGEX, '') }
- end
- end
- end
-end
diff --git a/lib/gitlab/gitlab_import/importer.rb b/lib/gitlab/gitlab_import/importer.rb
index 3f76ec97977..46d40f75be6 100644
--- a/lib/gitlab/gitlab_import/importer.rb
+++ b/lib/gitlab/gitlab_import/importer.rb
@@ -15,31 +15,35 @@ module Gitlab
end
def execute
- project_identifier = CGI.escape(project.import_source)
-
- # Issues && Comments
- issues = client.issues(project_identifier)
-
- issues.each do |issue|
- body = @formatter.author_line(issue["author"]["name"])
- body += issue["description"]
-
- comments = client.issue_comments(project_identifier, issue["id"])
-
- if comments.any?
- body += @formatter.comments_header
+ ActiveRecord::Base.no_touching do
+ project_identifier = CGI.escape(project.import_source)
+
+ # Issues && Comments
+ issues = client.issues(project_identifier)
+
+ issues.each do |issue|
+ body = @formatter.author_line(issue["author"]["name"])
+ body += issue["description"]
+
+ comments = client.issue_comments(project_identifier, issue["id"])
+
+ if comments.any?
+ body += @formatter.comments_header
+ end
+
+ comments.each do |comment|
+ body += @formatter.comment(comment["author"]["name"], comment["created_at"], comment["body"])
+ end
+
+ project.issues.create!(
+ iid: issue["iid"],
+ description: body,
+ title: issue["title"],
+ state: issue["state"],
+ updated_at: issue["updated_at"],
+ author_id: gl_user_id(project, issue["author"]["id"])
+ )
end
-
- comments.each do |comment|
- body += @formatter.comment(comment["author"]["name"], comment["created_at"], comment["body"])
- end
-
- project.issues.create!(
- description: body,
- title: issue["title"],
- state: issue["state"],
- author_id: gl_user_id(project, issue["author"]["id"])
- )
end
true
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index f751a3a12fd..c5a11148d33 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -3,10 +3,9 @@ module Gitlab
def add_gon_variables
gon.api_version = API::API.version
gon.default_avatar_url = URI::join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s
- gon.default_issues_tracker = Project.new.default_issue_tracker.to_param
gon.max_file_size = current_application_settings.max_attachment_size
gon.relative_url_root = Gitlab.config.gitlab.relative_url_root
- gon.shortcuts_path = help_shortcuts_path
+ gon.shortcuts_path = help_page_path('shortcuts')
gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class
gon.award_menu_url = emojis_path
diff --git a/lib/gitlab/graphs/commits.rb b/lib/gitlab/graphs/commits.rb
index 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..9360afedfcb 100644
--- a/lib/gitlab/highlight.rb
+++ b/lib/gitlab/highlight.rb
@@ -1,7 +1,7 @@
module Gitlab
class Highlight
- def self.highlight(blob_name, blob_content, nowrap: true, plain: false)
- new(blob_name, blob_content, nowrap: nowrap).
+ def self.highlight(blob_name, blob_content, repository: nil, plain: false)
+ new(blob_name, blob_content, repository: repository).
highlight(blob_content, continue: false, plain: plain)
end
@@ -10,35 +10,45 @@ module Gitlab
return [] unless blob
blob.load_all_data!(repository)
- highlight(file_name, blob.data).lines.map!(&:html_safe)
+ highlight(file_name, blob.data, repository: repository).lines.map!(&:html_safe)
end
- def initialize(blob_name, blob_content, nowrap: true)
- @formatter = rouge_formatter(nowrap: nowrap)
- @lexer = Rouge::Lexer.guess(filename: blob_name, source: blob_content).new rescue Rouge::Lexers::PlainText
+ def initialize(blob_name, blob_content, repository: nil)
+ @formatter = Rouge::Formatters::HTMLGitlab.new
+ @repository = repository
+ @blob_name = blob_name
+ @blob_content = blob_content
end
def highlight(text, continue: true, plain: false)
if plain
- @formatter.format(Rouge::Lexers::PlainText.lex(text)).html_safe
+ hl_lexer = Rouge::Lexers::PlainText
+ continue = false
else
- @formatter.format(@lexer.lex(text, continue: continue)).html_safe
+ hl_lexer = self.lexer
end
+
+ @formatter.format(hl_lexer.lex(text, continue: continue)).html_safe
rescue
@formatter.format(Rouge::Lexers::PlainText.lex(text)).html_safe
end
+ def lexer
+ @lexer ||= custom_language || begin
+ Rouge::Lexer.guess(filename: @blob_name, source: @blob_content).new
+ rescue Rouge::Guesser::Ambiguous => e
+ e.alternatives.sort_by(&:tag).first
+ end
+ end
+
private
- def rouge_formatter(options = {})
- options = options.reverse_merge(
- nowrap: true,
- cssclass: 'code highlight',
- lineanchors: true,
- lineanchorsid: 'LC'
- )
+ def custom_language
+ language_name = @repository && @repository.gitattribute(@blob_name, 'gitlab-language')
+
+ return nil unless language_name
- Rouge::Formatters::HTMLGitlab.new(options)
+ Rouge::Lexer.find_fancy(language_name)
end
end
end
diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb
index 99cf85d9a3b..d6d14bd98a0 100644
--- a/lib/gitlab/import_export.rb
+++ b/lib/gitlab/import_export.rb
@@ -2,7 +2,8 @@ module Gitlab
module ImportExport
extend self
- VERSION = '0.1.0'
+ VERSION = '0.1.2'
+ FILENAME_LIMIT = 50
def export_path(relative_path:)
File.join(storage_path, relative_path)
@@ -28,6 +29,12 @@ module Gitlab
'VERSION'
end
+ def export_filename(project:)
+ basename = "#{Time.now.strftime('%Y-%m-%d_%H-%M-%3N')}_#{project.namespace.path}_#{project.path}"
+
+ "#{basename[0..FILENAME_LIMIT]}_export.tar.gz"
+ end
+
def version
VERSION
end
diff --git a/lib/gitlab/import_export/avatar_restorer.rb b/lib/gitlab/import_export/avatar_restorer.rb
new file mode 100644
index 00000000000..352539eb594
--- /dev/null
+++ b/lib/gitlab/import_export/avatar_restorer.rb
@@ -0,0 +1,31 @@
+module Gitlab
+ module ImportExport
+ class AvatarRestorer
+
+ def initialize(project:, shared:)
+ @project = project
+ @shared = shared
+ end
+
+ def restore
+ return true unless avatar_export_file
+
+ @project.avatar = File.open(avatar_export_file)
+ @project.save!
+ rescue => e
+ @shared.error(e)
+ false
+ end
+
+ private
+
+ def avatar_export_file
+ @avatar_export_file ||= Dir["#{avatar_export_path}/*"].first
+ end
+
+ def avatar_export_path
+ File.join(@shared.export_path, 'avatar')
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/import_export/avatar_saver.rb b/lib/gitlab/import_export/avatar_saver.rb
new file mode 100644
index 00000000000..998c21e2586
--- /dev/null
+++ b/lib/gitlab/import_export/avatar_saver.rb
@@ -0,0 +1,31 @@
+module Gitlab
+ module ImportExport
+ class AvatarSaver
+ include Gitlab::ImportExport::CommandLineUtil
+
+ def initialize(project:, shared:)
+ @project = project
+ @shared = shared
+ end
+
+ def save
+ return true unless @project.avatar.exists?
+
+ copy_files(avatar_path, avatar_export_path)
+ rescue => e
+ @shared.error(e)
+ false
+ end
+
+ private
+
+ def avatar_export_path
+ File.join(@shared.export_path, 'avatar', @project.avatar_identifier)
+ end
+
+ def avatar_path
+ @project.avatar.path
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/import_export/command_line_util.rb b/lib/gitlab/import_export/command_line_util.rb
index 78664f076eb..5dd0e34c18e 100644
--- a/lib/gitlab/import_export/command_line_util.rb
+++ b/lib/gitlab/import_export/command_line_util.rb
@@ -28,13 +28,23 @@ module Gitlab
end
def execute(cmd)
- _output, status = Gitlab::Popen.popen(cmd)
+ output, status = Gitlab::Popen.popen(cmd)
+ @shared.error(Gitlab::ImportExport::Error.new(output.to_s)) unless status.zero?
status.zero?
end
def git_bin_path
Gitlab.config.git.bin_path
end
+
+ def copy_files(source, destination)
+ # if we are copying files, create the destination folder
+ destination_folder = File.file?(source) ? File.dirname(destination) : destination
+
+ FileUtils.mkdir_p(destination_folder)
+ FileUtils.copy_entry(source, destination)
+ true
+ end
end
end
end
diff --git a/lib/gitlab/import_export/file_importer.rb b/lib/gitlab/import_export/file_importer.rb
index 0e70d9282d5..82d1e1805c5 100644
--- a/lib/gitlab/import_export/file_importer.rb
+++ b/lib/gitlab/import_export/file_importer.rb
@@ -23,7 +23,11 @@ module Gitlab
private
def decompress_archive
- untar_zxf(archive: @archive_file, dir: @shared.export_path)
+ result = untar_zxf(archive: @archive_file, dir: @shared.export_path)
+
+ raise Projects::ImportService::Error.new("Unable to decompress #{@archive_file} into #{@shared.export_path}") unless result
+
+ true
end
end
end
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index 164ab6238c4..15afe8174a4 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
@@ -48,7 +53,11 @@ included_attributes:
excluded_attributes:
snippets:
- :expired_at
+ merge_request_diff:
+ - :st_diffs
methods:
statuses:
- - :type \ No newline at end of file
+ - :type
+ merge_request_diff:
+ - :utf8_st_diffs \ No newline at end of file
diff --git a/lib/gitlab/import_export/importer.rb b/lib/gitlab/import_export/importer.rb
index d209e04f7be..e9ee47fc090 100644
--- a/lib/gitlab/import_export/importer.rb
+++ b/lib/gitlab/import_export/importer.rb
@@ -1,7 +1,6 @@
module Gitlab
module ImportExport
class Importer
-
def initialize(project)
@archive_file = project.import_source
@current_user = project.creator
@@ -10,17 +9,22 @@ module Gitlab
end
def execute
- Gitlab::ImportExport::FileImporter.import(archive_file: @archive_file,
- shared: @shared)
- if check_version! && [project_tree, repo_restorer, wiki_restorer, uploads_restorer].all?(&:restore)
+ if import_file && check_version! && [project_tree, avatar_restorer, repo_restorer, wiki_restorer, uploads_restorer].all?(&:restore)
project_tree.restored_project
else
raise Projects::ImportService::Error.new(@shared.errors.join(', '))
end
+
+ remove_import_file
end
private
+ def import_file
+ Gitlab::ImportExport::FileImporter.import(archive_file: @archive_file,
+ shared: @shared)
+ end
+
def check_version!
Gitlab::ImportExport::VersionChecker.check!(shared: @shared)
end
@@ -31,6 +35,10 @@ module Gitlab
project: @project)
end
+ def avatar_restorer
+ Gitlab::ImportExport::AvatarRestorer.new(project: project_tree.restored_project, shared: @shared)
+ end
+
def repo_restorer
Gitlab::ImportExport::RepoRestorer.new(path_to_bundle: repo_path,
shared: @shared,
@@ -40,8 +48,7 @@ module Gitlab
def wiki_restorer
Gitlab::ImportExport::RepoRestorer.new(path_to_bundle: wiki_repo_path,
shared: @shared,
- project: ProjectWiki.new(project_tree.restored_project),
- wiki: true)
+ project: ProjectWiki.new(project_tree.restored_project))
end
def uploads_restorer
@@ -59,6 +66,10 @@ module Gitlab
def wiki_repo_path
File.join(@shared.export_path, 'project.wiki.bundle')
end
+
+ def remove_import_file
+ FileUtils.rm_rf(@archive_file)
+ end
end
end
end
diff --git a/lib/gitlab/import_export/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..051110c23cf 100644
--- a/lib/gitlab/import_export/project_tree_restorer.rb
+++ b/lib/gitlab/import_export/project_tree_restorer.rb
@@ -1,7 +1,6 @@
module Gitlab
module ImportExport
class ProjectTreeRestorer
-
def initialize(user:, shared:, project:)
@path = File.join(shared.export_path, 'project.json')
@user = user
@@ -13,7 +12,10 @@ module Gitlab
json = IO.read(@path)
@tree_hash = ActiveSupport::JSON.decode(json)
@project_members = @tree_hash.delete('project_members')
- create_relations
+
+ ActiveRecord::Base.no_touching do
+ create_relations
+ end
rescue => e
@shared.error(e)
false
@@ -70,10 +72,19 @@ module Gitlab
# Example:
# +relation_key+ issues, loops through the list of *issues* and for each individual
# issue, finds any subrelations such as notes, creates them and assign them back to the hash
+ #
+ # Recursively calls this method if the sub-relation is a hash containing more sub-relations
def create_sub_relations(relation, tree_hash)
relation_key = relation.keys.first.to_s
+ return if tree_hash[relation_key].blank?
+
tree_hash[relation_key].each do |relation_item|
relation.values.flatten.each do |sub_relation|
+ # We just use author to get the user ID, do not attempt to create an instance.
+ next if sub_relation == :author
+
+ create_sub_relations(sub_relation, relation_item) if sub_relation.is_a?(Hash)
+
relation_hash, sub_relation = assign_relation_hash(relation_item, sub_relation)
relation_item[sub_relation.to_s] = create_relation(sub_relation, relation_hash) unless relation_hash.blank?
end
diff --git a/lib/gitlab/import_export/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 b872780f20a..e41c7e6bf4f 100644
--- a/lib/gitlab/import_export/relation_factory.rb
+++ b/lib/gitlab/import_export/relation_factory.rb
@@ -1,7 +1,6 @@
module Gitlab
module ImportExport
class RelationFactory
-
OVERRIDES = { snippets: :project_snippets,
pipelines: 'Ci::Pipeline',
statuses: 'commit_status',
@@ -12,6 +11,8 @@ module Gitlab
USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id].freeze
+ BUILD_MODELS = %w[Ci::Build commit_status].freeze
+
def self.create(*args)
new(*args).create
end
@@ -31,6 +32,8 @@ 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']
+ set_st_diffs if @relation_name == :merge_request_diff
generate_imported_object
end
@@ -70,7 +73,7 @@ module Gitlab
end
def generate_imported_object
- if @relation_sym == 'commit_status' # call #trace= method after assigning the other attributes
+ if BUILD_MODELS.include?(@relation_name) # call #trace= method after assigning the other attributes
trace = @relation_hash.delete('trace')
imported_object do |object|
object.trace = trace
@@ -85,7 +88,7 @@ module Gitlab
project_id = @relation_hash.delete('project_id')
# project_id may not be part of the export, but we always need to populate it if required.
- @relation_hash['project_id'] = project_id if relation_class.column_names.include?('project_id')
+ @relation_hash['project_id'] = project_id
@relation_hash['gl_project_id'] = project_id if @relation_hash['gl_project_id']
@relation_hash['target_project_id'] = project_id if @relation_hash['target_project_id']
@relation_hash['source_project_id'] = -1 if @relation_hash['source_project_id']
@@ -109,7 +112,7 @@ module Gitlab
end
def imported_object
- imported_object = relation_class.new(@relation_hash)
+ imported_object = relation_class.new(parsed_relation_hash)
yield(imported_object) if block_given?
imported_object.importing = true if imported_object.respond_to?(:importing)
imported_object
@@ -123,6 +126,14 @@ module Gitlab
def admin_user?
@user.is_admin?
end
+
+ def parsed_relation_hash
+ @relation_hash.reject { |k, _v| !relation_class.attribute_method?(k) }
+ end
+
+ def set_st_diffs
+ @relation_hash['st_diffs'] = @relation_hash.delete('utf8_st_diffs')
+ end
end
end
end
diff --git a/lib/gitlab/import_export/repo_restorer.rb b/lib/gitlab/import_export/repo_restorer.rb
index 546dae4d122..f84de652a57 100644
--- a/lib/gitlab/import_export/repo_restorer.rb
+++ b/lib/gitlab/import_export/repo_restorer.rb
@@ -3,15 +3,14 @@ module Gitlab
class RepoRestorer
include Gitlab::ImportExport::CommandLineUtil
- def initialize(project:, shared:, path_to_bundle:, wiki: false)
+ def initialize(project:, shared:, path_to_bundle:)
@project = project
@path_to_bundle = path_to_bundle
@shared = shared
- @wiki = wiki
end
def restore
- return wiki? unless File.exist?(@path_to_bundle)
+ return true unless File.exist?(@path_to_bundle)
FileUtils.mkdir_p(path_to_repo)
@@ -30,10 +29,6 @@ module Gitlab
def path_to_repo
@project.repository.path_to_repo
end
-
- def wiki?
- @wiki
- end
end
end
end
diff --git a/lib/gitlab/import_export/repo_saver.rb b/lib/gitlab/import_export/repo_saver.rb
index cce43fe994b..331e14021e6 100644
--- a/lib/gitlab/import_export/repo_saver.rb
+++ b/lib/gitlab/import_export/repo_saver.rb
@@ -11,7 +11,7 @@ module Gitlab
end
def save
- return false if @project.empty_repo?
+ return true if @project.empty_repo? # it's ok to have no repo
@full_path = File.join(@shared.export_path, ImportExport.project_bundle_filename)
bundle_to_disk
diff --git a/lib/gitlab/import_export/saver.rb b/lib/gitlab/import_export/saver.rb
index f38229c6c59..6130c124dd1 100644
--- a/lib/gitlab/import_export/saver.rb
+++ b/lib/gitlab/import_export/saver.rb
@@ -7,7 +7,8 @@ module Gitlab
new(*args).save
end
- def initialize(shared:)
+ def initialize(project:, shared:)
+ @project = project
@shared = shared
end
@@ -17,6 +18,7 @@ module Gitlab
Rails.logger.info("Saved project export #{archive_file}")
archive_file
else
+ @shared.error(Gitlab::ImportExport::Error.new("Unable to save #{archive_file} into #{@shared.export_path}"))
false
end
rescue => e
@@ -35,7 +37,7 @@ module Gitlab
end
def archive_file
- @archive_file ||= File.join(@shared.export_path, '..', "#{Time.now.strftime('%Y-%m-%d_%H-%M-%3N')}_project_export.tar.gz")
+ @archive_file ||= File.join(@shared.export_path, '..', Gitlab::ImportExport.export_filename(project: @project))
end
end
end
diff --git a/lib/gitlab/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..62a2553675c 100644
--- a/lib/gitlab/import_export/uploads_saver.rb
+++ b/lib/gitlab/import_export/uploads_saver.rb
@@ -1,6 +1,7 @@
module Gitlab
module ImportExport
class UploadsSaver
+ include Gitlab::ImportExport::CommandLineUtil
def initialize(project:, shared:)
@project = project
@@ -18,12 +19,6 @@ module Gitlab
private
- def copy_files(source, destination)
- FileUtils.mkdir_p(destination)
- FileUtils.copy_entry(source, destination)
- true
- end
-
def uploads_export_path
File.join(@shared.export_path, 'uploads')
end
diff --git a/lib/gitlab/import_export/version_checker.rb b/lib/gitlab/import_export/version_checker.rb
index 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_export/wiki_repo_saver.rb b/lib/gitlab/import_export/wiki_repo_saver.rb
index 1eedae39f8a..6107420e4dd 100644
--- a/lib/gitlab/import_export/wiki_repo_saver.rb
+++ b/lib/gitlab/import_export/wiki_repo_saver.rb
@@ -4,6 +4,7 @@ module Gitlab
def save
@wiki = ProjectWiki.new(@project)
return true unless wiki_repository_exists? # it's okay to have no Wiki
+
bundle_to_disk(File.join(@shared.export_path, project_filename))
end
diff --git a/lib/gitlab/import_sources.rb b/lib/gitlab/import_sources.rb
index 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 9d9617761b3..a1ee1aa81ff 100644
--- a/lib/gitlab/lfs/response.rb
+++ b/lib/gitlab/lfs/response.rb
@@ -1,11 +1,11 @@
module Gitlab
module Lfs
class Response
-
- def initialize(project, user, request)
+ def initialize(project, user, ci, request)
@origin_project = project
@project = storage_project(project)
@user = user
+ @ci = ci
@env = request.env
@request = request
end
@@ -47,6 +47,8 @@ module Gitlab
end
def render_storage_upload_store_response(oid, size, tmp_file_name)
+ return render_forbidden unless tmp_file_name
+
render_response_to_push do
render_lfs_upload_ok(oid, size, tmp_file_name)
end
@@ -189,7 +191,7 @@ module Gitlab
return render_not_enabled unless Gitlab.config.lfs.enabled
unless @project.public?
- return render_unauthorized unless @user
+ return render_unauthorized unless @user || @ci
return render_forbidden unless user_can_fetch?
end
@@ -210,7 +212,7 @@ module Gitlab
def user_can_fetch?
# Check user access against the project they used to initiate the pull
- @user.can?(:download_code, @origin_project)
+ @ci || @user.can?(:download_code, @origin_project)
end
def user_can_push?
diff --git a/lib/gitlab/lfs/router.rb b/lib/gitlab/lfs/router.rb
index 78d02891102..f2a76a56b8f 100644
--- a/lib/gitlab/lfs/router.rb
+++ b/lib/gitlab/lfs/router.rb
@@ -1,9 +1,12 @@
module Gitlab
module Lfs
class Router
- def initialize(project, user, request)
+ attr_reader :project, :user, :ci, :request
+
+ def initialize(project, user, ci, request)
@project = project
@user = user
+ @ci = ci
@env = request.env
@request = request
end
@@ -71,8 +74,6 @@ module Gitlab
lfs.render_storage_upload_authorize_response(oid, size)
else
tmp_file_name = sanitize_tmp_filename(@request.env['HTTP_X_GITLAB_LFS_TMP'])
- return nil unless tmp_file_name
-
lfs.render_storage_upload_store_response(oid, size, tmp_file_name)
end
end
@@ -80,7 +81,7 @@ module Gitlab
def lfs
return unless @project
- Gitlab::Lfs::Response.new(@project, @user, @request)
+ Gitlab::Lfs::Response.new(@project, @user, @ci, @request)
end
def sanitize_tmp_filename(name)
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/sidekiq_middleware.rb b/lib/gitlab/metrics/sidekiq_middleware.rb
index fd98aa3412e..a1240fd33ee 100644
--- a/lib/gitlab/metrics/sidekiq_middleware.rb
+++ b/lib/gitlab/metrics/sidekiq_middleware.rb
@@ -8,6 +8,8 @@ module Gitlab
trans = Transaction.new("#{worker.class.name}#perform")
begin
+ # Old gitlad-shell messages don't provide enqueued_at/created_at attributes
+ trans.set(:sidekiq_queue_duration, Time.now.to_f - (message['enqueued_at'] || message['created_at'] || 0))
trans.run { yield }
ensure
trans.finish
diff --git a/lib/gitlab/metrics/subscribers/rails_cache.rb b/lib/gitlab/metrics/subscribers/rails_cache.rb
index 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 78f3ecb4cb4..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
@@ -74,7 +72,7 @@ module Gitlab
if user
# Case when a LDAP user already exists in Gitlab. Add the OAuth identity to existing account.
log.info "LDAP account found for user #{user.username}. Building new #{auth_hash.provider} identity."
- user.identities.build(extern_uid: auth_hash.uid, provider: auth_hash.provider)
+ user.identities.find_or_initialize_by(extern_uid: auth_hash.uid, provider: auth_hash.provider)
else
log.info "No existing LDAP account was found in GitLab. Checking for #{auth_hash.provider} account."
user = find_by_uid_and_provider
diff --git a/lib/gitlab/other_markup.rb b/lib/gitlab/other_markup.rb
index 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/template/base_template.rb b/lib/gitlab/template/base_template.rb
new file mode 100644
index 00000000000..760ff3e614a
--- /dev/null
+++ b/lib/gitlab/template/base_template.rb
@@ -0,0 +1,67 @@
+module Gitlab
+ module Template
+ class BaseTemplate
+ def initialize(path)
+ @path = path
+ end
+
+ def name
+ File.basename(@path, self.class.extension)
+ end
+
+ def content
+ File.read(@path)
+ end
+
+ class << self
+ def all
+ self.categories.keys.flat_map { |cat| by_category(cat) }
+ end
+
+ def find(key)
+ file_name = "#{key}#{self.extension}"
+
+ directory = select_directory(file_name)
+ directory ? new(File.join(category_directory(directory), file_name)) : nil
+ end
+
+ def categories
+ raise NotImplementedError
+ end
+
+ def extension
+ raise NotImplementedError
+ end
+
+ def base_dir
+ raise NotImplementedError
+ end
+
+ def by_category(category)
+ templates_for_directory(category_directory(category))
+ end
+
+ def category_directory(category)
+ File.join(base_dir, categories[category])
+ end
+
+ private
+
+ def select_directory(file_name)
+ categories.keys.find do |category|
+ File.exist?(File.join(category_directory(category), file_name))
+ end
+ end
+
+ def templates_for_directory(dir)
+ dir << '/' unless dir.end_with?('/')
+ Dir.glob(File.join(dir, "*#{self.extension}")).select { |f| f =~ filter_regex }.map { |f| new(f) }
+ end
+
+ def filter_regex
+ @filter_reges ||= /#{Regexp.escape(extension)}\z/
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/template/gitignore.rb b/lib/gitlab/template/gitignore.rb
new file mode 100644
index 00000000000..964fbfd4de3
--- /dev/null
+++ b/lib/gitlab/template/gitignore.rb
@@ -0,0 +1,22 @@
+module Gitlab
+ module Template
+ class Gitignore < BaseTemplate
+ class << self
+ def extension
+ '.gitignore'
+ end
+
+ def categories
+ {
+ "Languages" => '',
+ "Global" => 'Global'
+ }
+ end
+
+ def base_dir
+ Rails.root.join('vendor/gitignore')
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/template/gitlab_ci_yml.rb b/lib/gitlab/template/gitlab_ci_yml.rb
new file mode 100644
index 00000000000..7f480fe33c0
--- /dev/null
+++ b/lib/gitlab/template/gitlab_ci_yml.rb
@@ -0,0 +1,27 @@
+module Gitlab
+ module Template
+ class GitlabCiYml < BaseTemplate
+ def content
+ explanation = "# This file is a template, and might need editing before it works on your project."
+ [explanation, super].join("\n")
+ end
+
+ class << self
+ def extension
+ '.gitlab-ci.yml'
+ end
+
+ def categories
+ {
+ "General" => '',
+ "Pages" => 'Pages'
+ }
+ end
+
+ def base_dir
+ Rails.root.join('vendor/gitlab-ci-yml')
+ end
+ end
+ end
+ 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..19dad699edf 100644
--- a/lib/gitlab/url_sanitizer.rb
+++ b/lib/gitlab/url_sanitizer.rb
@@ -4,10 +4,20 @@ module Gitlab
regexp = URI::Parser.new.make_regexp(['http', 'https', 'ssh', 'git'])
content.gsub(regexp) { |url| new(url).masked_url }
+ rescue Addressable::URI::InvalidURIError
+ content.gsub(regexp, '')
+ end
+
+ def self.valid?(url)
+ Addressable::URI.parse(url.strip)
+
+ true
+ rescue Addressable::URI::InvalidURIError
+ false
end
def initialize(url, credentials: nil)
- @url = Addressable::URI.parse(url)
+ @url = Addressable::URI.parse(url.strip)
@credentials = credentials
end
diff --git a/lib/gitlab/user_access.rb b/lib/gitlab/user_access.rb
index d1b42c1f9b9..c0f85e9b3a8 100644
--- a/lib/gitlab/user_access.rb
+++ b/lib/gitlab/user_access.rb
@@ -1,7 +1,23 @@
module Gitlab
- module UserAccess
- def self.allowed?(user)
- return false if user.blocked?
+ class UserAccess
+ attr_reader :user, :project
+
+ def initialize(user, project: nil)
+ @user = user
+ @project = project
+ end
+
+ def can_do_action?(action)
+ @permission_cache ||= {}
+ @permission_cache[action] ||= user.can?(action, project)
+ end
+
+ def cannot_do_action?(action)
+ !can_do_action?(action)
+ end
+
+ def allowed?
+ return false if user.blank? || user.blocked?
if user.requires_ldap_check? && user.try_obtain_ldap_lease
return false unless Gitlab::LDAP::Access.allowed?(user)
@@ -9,5 +25,31 @@ module Gitlab
true
end
+
+ def can_push_to_branch?(ref)
+ return false unless user
+
+ if project.protected_branch?(ref) && !project.developers_can_push_to_protected_branch?(ref)
+ user.can?(:push_code_to_protected_branches, project)
+ else
+ user.can?(:push_code, project)
+ end
+ end
+
+ def can_merge_to_branch?(ref)
+ return false unless user
+
+ if project.protected_branch?(ref) && !project.developers_can_merge_to_protected_branch?(ref)
+ user.can?(:push_code_to_protected_branches, project)
+ else
+ user.can?(:push_code, project)
+ end
+ end
+
+ def can_read_project?
+ return false unless user
+
+ user.can?(:read_project, project)
+ end
end
end
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index 40e8299c36b..6aeb49c0219 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -38,12 +38,10 @@ module Gitlab
end
def send_git_diff(repository, diff_refs)
- from, to = diff_refs
-
params = {
'RepoPath' => repository.path_to_repo,
- 'ShaFrom' => from.sha,
- 'ShaTo' => to.sha
+ 'ShaFrom' => diff_refs.start_sha,
+ 'ShaTo' => diff_refs.head_sha
}
[
@@ -52,6 +50,31 @@ module Gitlab
]
end
+ def send_git_patch(repository, diff_refs)
+ params = {
+ 'RepoPath' => repository.path_to_repo,
+ 'ShaFrom' => diff_refs.start_sha,
+ 'ShaTo' => diff_refs.head_sha
+ }
+
+ [
+ SEND_DATA_HEADER,
+ "git-format-patch:#{encode(params)}"
+ ]
+ end
+
+ def send_artifacts_entry(build, entry)
+ params = {
+ 'Archive' => build.artifacts_file.path,
+ 'Entry' => Base64.encode64(entry.path)
+ }
+
+ [
+ SEND_DATA_HEADER,
+ "artifacts-entry:#{encode(params)}"
+ ]
+ end
+
protected
def encode(hash)
diff --git a/lib/rouge/formatters/html_gitlab.rb b/lib/rouge/formatters/html_gitlab.rb
index 8c309efc7b8..f818dc78d34 100644
--- a/lib/rouge/formatters/html_gitlab.rb
+++ b/lib/rouge/formatters/html_gitlab.rb
@@ -1,175 +1,27 @@
-require 'cgi'
-
module Rouge
module Formatters
- class HTMLGitlab < Rouge::Formatter
+ class HTMLGitlab < Rouge::Formatters::HTML
tag 'html_gitlab'
# Creates a new <tt>Rouge::Formatter::HTMLGitlab</tt> instance.
#
- # [+nowrap+] If set to True, don't wrap the output at all, not
- # even inside a <tt><pre></tt> tag (default: false).
- # [+cssclass+] CSS class for the wrapping <tt><div></tt> tag
- # (default: 'highlight').
- # [+linenos+] If set to 'table', output line numbers as a table
- # with two cells, one containing the line numbers,
- # the other the whole code. This is copy paste friendly,
- # but may cause alignment problems with some browsers
- # or fonts. If set to 'inline', the line numbers will
- # be integrated in the <tt><pre></tt> tag that contains
- # the code (default: nil).
# [+linenostart+] The line number for the first line (default: 1).
- # [+lineanchors+] If set to true the formatter will wrap each output
- # line in an anchor tag with a name of L-linenumber.
- # This allows easy linking to certain lines
- # (default: false).
- # [+lineanchorsid+] If lineanchors is true the name of the anchors can
- # be changed with lineanchorsid to e.g. foo-linenumber
- # (default: 'L').
- # [+anchorlinenos+] If set to true, will wrap line numbers in <tt><a></tt>
- # tags. Used in combination with linenos and lineanchors
- # (default: false).
- # [+inline_theme+] Inline CSS styles for the <pre> tag (default: false).
- def initialize(
- nowrap: false,
- cssclass: 'highlight',
- linenos: nil,
- linenostart: 1,
- lineanchors: false,
- lineanchorsid: 'L',
- anchorlinenos: false,
- inline_theme: nil
- )
- @nowrap = nowrap
- @cssclass = cssclass
- @linenos = linenos
+ def initialize(linenostart: 1)
@linenostart = linenostart
- @lineanchors = lineanchors
- @lineanchorsid = lineanchorsid
- @anchorlinenos = anchorlinenos
- @inline_theme = Theme.find(inline_theme).new if inline_theme.is_a?(String)
- end
-
- def render(tokens)
- case @linenos
- when 'table'
- render_tableized(tokens)
- when 'inline'
- render_untableized(tokens)
- else
- render_untableized(tokens)
- end
- end
-
- alias_method :format, :render
-
- private
-
- def render_untableized(tokens)
- data = process_tokens(tokens)
-
- html = ''
- html << "<pre class=\"#{@cssclass}\"><code>" unless @nowrap
- html << wrap_lines(data[:code])
- html << "</code></pre>\n" unless @nowrap
- html
- end
-
- def render_tableized(tokens)
- data = process_tokens(tokens)
-
- html = ''
- html << "<div class=\"#{@cssclass}\">" unless @nowrap
- html << '<table><tbody>'
- html << "<td class=\"linenos\"><pre>"
- html << wrap_linenos(data[:numbers])
- html << '</pre></td>'
- html << "<td class=\"lines\"><pre><code>"
- html << wrap_lines(data[:code])
- html << '</code></pre></td>'
- html << '</tbody></table>'
- html << '</div>' unless @nowrap
- html
- end
-
- def process_tokens(tokens)
- rendered = []
- current_line = ''
-
- tokens.each do |tok, val|
- # In the case of multi-line values (e.g. comments), we need to apply
- # styling to each line since span elements are inline.
- val.lines.each do |line|
- stripped = line.chomp
- current_line << span(tok, stripped)
-
- if line.end_with?("\n")
- rendered << current_line
- current_line = ''
- end
- end
- end
-
- # Add leftover text
- rendered << current_line if current_line.present?
-
- num_lines = rendered.size
- numbers = (@linenostart..num_lines + @linenostart - 1).to_a
-
- { numbers: numbers, code: rendered }
+ @line_number = linenostart
end
- def wrap_linenos(numbers)
- if @anchorlinenos
- numbers.map! do |number|
- "<a href=\"##{@lineanchorsid}#{number}\">#{number}</a>"
- end
- end
- numbers.join("\n")
- end
-
- def wrap_lines(lines)
- if @lineanchors
- lines = lines.each_with_index.map do |line, index|
- number = index + @linenostart
-
- if @linenos == 'inline'
- "<a name=\"L#{number}\"></a>" \
- "<span class=\"linenos\">#{number}</span>" \
- "<span id=\"#{@lineanchorsid}#{number}\" class=\"line\">#{line}" \
- '</span>'
- else
- "<span id=\"#{@lineanchorsid}#{number}\" class=\"line\">#{line}" \
- '</span>'
- end
- end
- lines.join("\n")
- else
- if @linenos == 'inline'
- lines = lines.each_with_index.map do |line, index|
- number = index + @linenostart
- "<span class=\"linenos\">#{number}</span>#{line}"
- end
- lines.join("\n")
- else
- lines.join("\n")
- end
- end
- end
+ def stream(tokens, &b)
+ is_first = true
+ token_lines(tokens) do |line|
+ yield "\n" unless is_first
+ is_first = false
- def span(tok, val)
- # http://stackoverflow.com/a/1600584/2587286
- val = CGI.escapeHTML(val)
+ yield %(<span id="LC#{@line_number}" class="line">)
+ line.each { |token, value| yield span(token, value) }
+ yield %(</span>)
- if tok.shortname.empty?
- val
- else
- if @inline_theme
- rules = @inline_theme.style_for(tok).rendered_rules
- "<span style=\"#{rules.to_a.join(';')}\"#{val}</span>"
- else
- "<span class=\"#{tok.shortname}\">#{val}</span>"
- end
+ @line_number += 1
end
end
end
diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab
index d521de28e8a..4a4892a2e07 100644
--- a/lib/support/nginx/gitlab
+++ b/lib/support/nginx/gitlab
@@ -49,7 +49,12 @@ server {
proxy_http_version 1.1;
- proxy_set_header Host $http_host;
+ ## By overwriting Host and clearing X-Forwarded-Host we ensure that
+ ## internal HTTP redirects generated by GitLab always send users to
+ ## YOUR_SERVER_FQDN.
+ proxy_set_header Host YOUR_SERVER_FQDN;
+ proxy_set_header X-Forwarded-Host "";
+
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl
index bf014b56cf6..0b93d7f292f 100644
--- a/lib/support/nginx/gitlab-ssl
+++ b/lib/support/nginx/gitlab-ssl
@@ -93,7 +93,12 @@ server {
proxy_http_version 1.1;
- proxy_set_header Host $http_host;
+ ## By overwriting Host and clearing X-Forwarded-Host we ensure that
+ ## internal HTTP redirects generated by GitLab always send users to
+ ## YOUR_SERVER_FQDN.
+ proxy_set_header Host YOUR_SERVER_FQDN;
+ proxy_set_header X-Forwarded-Host "";
+
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Ssl on;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
diff --git a/lib/tasks/gemojione.rake b/lib/tasks/gemojione.rake
index 030ee8bafcb..993112aee3b 100644
--- a/lib/tasks/gemojione.rake
+++ b/lib/tasks/gemojione.rake
@@ -4,7 +4,7 @@ namespace :gemojione do
require 'digest/sha2'
require 'json'
- dir = Gemojione.index.images_path
+ dir = Gemojione.images_path
digests = []
aliases = Hash.new { |hash, key| hash[key] = [] }
aliases_path = File.join(Rails.root, 'fixtures', 'emojis', 'aliases.json')
@@ -13,7 +13,7 @@ namespace :gemojione do
aliases[real_name] << alias_name
end
- AwardEmoji.emojis.map do |name, emoji_hash|
+ Gitlab::AwardEmoji.emojis.map do |name, emoji_hash|
fpath = File.join(dir, "#{emoji_hash['unicode']}.png")
digest = Digest::SHA256.file(fpath).hexdigest
@@ -50,9 +50,14 @@ namespace :gemojione do
SIZE = 20
RETINA = SIZE * 2
+ # Update these values to the width and height of the spritesheet when
+ # new emoji are added.
+ SPRITESHEET_WIDTH = 860
+ SPRITESHEET_HEIGHT = 840
+
Dir.mktmpdir do |tmpdir|
# Copy the Gemojione assets to the temporary folder for resizing
- FileUtils.cp_r(Gemojione.index.images_path, tmpdir)
+ FileUtils.cp_r(Gemojione.images_path, tmpdir)
Dir.chdir(tmpdir) do
Dir["**/*.png"].each do |png|
@@ -64,7 +69,7 @@ namespace :gemojione do
# Combine the resized assets into a packed sprite and re-generate the SCSS
SpriteFactory.cssurl = "image-url('$IMAGE')"
- SpriteFactory.run!(File.join(tmpdir, 'images'), {
+ SpriteFactory.run!(File.join(tmpdir, 'png'), {
output_style: style_path,
output_image: "app/assets/images/emoji.png",
selector: '.emoji-',
@@ -97,7 +102,7 @@ namespace :gemojione do
only screen and (min-resolution: 192dpi),
only screen and (min-resolution: 2dppx) {
background-image: image-url('emoji@2x.png');
- background-size: 840px 820px;
+ background-size: #{SPRITESHEET_WIDTH}px #{SPRITESHEET_HEIGHT}px;
}
}
CSS
@@ -107,7 +112,7 @@ namespace :gemojione do
# Now do it again but for Retina
Dir.mktmpdir do |tmpdir|
# Copy the Gemojione assets to the temporary folder for resizing
- FileUtils.cp_r(Gemojione.index.images_path, tmpdir)
+ FileUtils.cp_r(Gemojione.images_path, tmpdir)
Dir.chdir(tmpdir) do
Dir["**/*.png"].each do |png|
@@ -116,7 +121,7 @@ namespace :gemojione do
end
# Combine the resized assets into a packed sprite and re-generate the SCSS
- SpriteFactory.run!(File.join(tmpdir, 'images'), {
+ SpriteFactory.run!(File.join(tmpdir), {
output_image: "app/assets/images/emoji@2x.png",
style: false,
nocomments: true,
diff --git a/lib/tasks/gitlab/backup.rake b/lib/tasks/gitlab/backup.rake
index 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/import_export.rake b/lib/tasks/gitlab/import_export.rake
new file mode 100644
index 00000000000..c2c6031db67
--- /dev/null
+++ b/lib/tasks/gitlab/import_export.rake
@@ -0,0 +1,13 @@
+namespace :gitlab do
+ namespace :import_export do
+ desc "GitLab | Show Import/Export version"
+ task version: :environment do
+ puts "Import/Export v#{Gitlab::ImportExport.version}"
+ end
+
+ desc "GitLab | Display exported DB structure"
+ task data: :environment do
+ puts YAML.load_file(Gitlab::ImportExport.config_file)['project_tree'].to_yaml(:SortKeys => true)
+ end
+ end
+end
diff --git a/lib/tasks/gitlab/info.rake b/lib/tasks/gitlab/info.rake
index 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/gitlab/track_deployment.rake b/lib/tasks/gitlab/track_deployment.rake
new file mode 100644
index 00000000000..84aa2e8507a
--- /dev/null
+++ b/lib/tasks/gitlab/track_deployment.rake
@@ -0,0 +1,9 @@
+namespace :gitlab do
+ desc 'GitLab | Tracks a deployment in GitLab Performance Monitoring'
+ task track_deployment: :environment do
+ metric = Gitlab::Metrics::Metric.
+ new('deployments', version: Gitlab::VERSION)
+
+ Gitlab::Metrics.submit_metrics([metric.to_hash])
+ end
+end
diff --git a/lib/tasks/gitlab/update_gitignore.rake b/lib/tasks/gitlab/update_gitignore.rake
deleted file mode 100644
index 4fd48cccb1d..00000000000
--- a/lib/tasks/gitlab/update_gitignore.rake
+++ /dev/null
@@ -1,46 +0,0 @@
-namespace :gitlab do
- desc "GitLab | Update gitignore"
- task :update_gitignore do
- unless clone_gitignores
- puts "Cloning the gitignores failed".color(:red)
- return
- end
-
- remove_unneeded_files(gitignore_directory)
- remove_unneeded_files(global_directory)
-
- puts "Done".color(:green)
- end
-
- def clone_gitignores
- FileUtils.rm_rf(gitignore_directory) if Dir.exist?(gitignore_directory)
- FileUtils.cd vendor_directory
-
- system('git clone --depth=1 --branch=master https://github.com/github/gitignore.git')
- end
-
- # Retain only certain files:
- # - The LICENSE, because we have to
- # - The sub dir global
- # - The gitignores themself
- # - Dir.entires returns also the entries '.' and '..'
- def remove_unneeded_files(path)
- Dir.foreach(path) do |file|
- FileUtils.rm_rf(File.join(path, file)) unless file =~ /(\.{1,2}|LICENSE|Global|\.gitignore)\z/
- end
- end
-
- private
-
- def vendor_directory
- Rails.root.join('vendor')
- end
-
- def gitignore_directory
- File.join(vendor_directory, 'gitignore')
- end
-
- def global_directory
- File.join(gitignore_directory, 'Global')
- end
-end
diff --git a/lib/tasks/gitlab/update_templates.rake b/lib/tasks/gitlab/update_templates.rake
new file mode 100644
index 00000000000..4f76dad7286
--- /dev/null
+++ b/lib/tasks/gitlab/update_templates.rake
@@ -0,0 +1,54 @@
+namespace :gitlab do
+ desc "GitLab | Update templates"
+ task :update_templates do
+ TEMPLATE_DATA.each { |template| update(template) }
+ end
+
+ def update(template)
+ sub_dir = template.repo_url.match(/([a-z-]+)\.git\z/)[1]
+ dir = File.join(vendor_directory, sub_dir)
+
+ unless clone_repository(template.repo_url, dir)
+ puts "Cloning the #{sub_dir} templates failed".red
+ return
+ end
+
+ remove_unneeded_files(dir, template.cleanup_regex)
+ puts "Done".green
+ end
+
+ def clone_repository(url, directory)
+ FileUtils.rm_rf(directory) if Dir.exist?(directory)
+
+ system("git clone #{url} --depth=1 --branch=master #{directory}")
+ end
+
+ # Retain only certain files:
+ # - The LICENSE, because we have to
+ # - The sub dirs so we can organise the file by category
+ # - The templates themself
+ # - Dir.entries returns also the entries '.' and '..'
+ def remove_unneeded_files(directory, regex)
+ Dir.foreach(directory) do |file|
+ FileUtils.rm_rf(File.join(directory, file)) unless file =~ regex
+ end
+ end
+
+ private
+
+ Template = Struct.new(:repo_url, :cleanup_regex)
+ TEMPLATE_DATA = [
+ Template.new(
+ "https://github.com/github/gitignore.git",
+ /(\.{1,2}|LICENSE|Global|\.gitignore)\z/
+ ),
+ Template.new(
+ "https://gitlab.com/gitlab-org/gitlab-ci-yml.git",
+ /(\.{1,2}|LICENSE|Pages|\.gitlab-ci.yml)\z/
+ )
+ ]
+
+ def vendor_directory
+ Rails.root.join('vendor')
+ 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
index 7da5f23ed9b..05c8b0d0ccf 100644
--- a/public/apple-touch-icon-precomposed.png
+++ b/public/apple-touch-icon-precomposed.png
Binary files differ
diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png
index 7da5f23ed9b..05c8b0d0ccf 100644
--- a/public/apple-touch-icon.png
+++ b/public/apple-touch-icon.png
Binary files differ
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
deleted file mode 100644
index cf5c606c723..00000000000
--- a/spec/controllers/commit_controller_spec.rb
+++ /dev/null
@@ -1,246 +0,0 @@
-require 'spec_helper'
-
-describe Projects::CommitController do
- let(:project) { create(:project) }
- let(:user) { create(:user) }
- let(:commit) { project.commit("master") }
- let(:master_pickable_sha) { '7d3b0f7cff5f37573aea97cebfd5692ea1689924' }
- let(:master_pickable_commit) { project.commit(master_pickable_sha) }
-
- before do
- sign_in(user)
- project.team << [user, :master]
- end
-
- describe "#show" do
- shared_examples "export as" do |format|
- it "should generally work" do
- get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: commit.id,
- format: format)
-
- expect(response).to be_success
- end
-
- it "should generate it" do
- expect_any_instance_of(Commit).to receive(:"to_#{format}")
-
- get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: commit.id, format: format)
- end
-
- it "should render it" do
- get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: commit.id, format: format)
-
- expect(response.body).to eq(commit.send(:"to_#{format}"))
- end
-
- it "should not escape Html" do
- allow_any_instance_of(Commit).to receive(:"to_#{format}").
- and_return('HTML entities &<>" ')
-
- get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: commit.id, format: format)
-
- expect(response.body).not_to include('&amp;')
- expect(response.body).not_to include('&gt;')
- expect(response.body).not_to include('&lt;')
- expect(response.body).not_to include('&quot;')
- end
- end
-
- describe "as diff" do
- include_examples "export as", :diff
- let(:format) { :diff }
-
- it "should really only be a git diff" do
- get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: commit.id,
- format: format)
-
- expect(response.body).to start_with("diff --git")
- end
-
- it "should really only be a git diff without whitespace changes" do
- get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: '66eceea0db202bb39c4e445e8ca28689645366c5',
- # id: commit.id,
- format: format,
- w: 1)
-
- expect(response.body).to start_with("diff --git")
- # without whitespace option, there are more than 2 diff_splits
- diff_splits = assigns(:diffs).first.diff.split("\n")
- expect(diff_splits.length).to be <= 2
- end
- end
-
- describe "as patch" do
- include_examples "export as", :patch
- let(:format) { :patch }
-
- it "should really be a git email patch" do
- get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: commit.id,
- format: format)
-
- expect(response.body).to start_with("From #{commit.id}")
- end
-
- it "should contain a git diff" do
- get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: commit.id,
- format: format)
-
- expect(response.body).to match(/^diff --git/)
- end
- end
-
- context 'commit that removes a submodule' do
- render_views
-
- let(:fork_project) { create(:forked_project_with_submodules) }
- let(:commit) { fork_project.commit('remove-submodule') }
-
- before do
- fork_project.team << [user, :master]
- end
-
- it 'renders it' do
- get(:show,
- namespace_id: fork_project.namespace.to_param,
- project_id: fork_project.to_param,
- id: commit.id)
-
- expect(response).to be_success
- end
- end
- end
-
- describe "#branches" do
- it "contains branch and tags information" do
- get(:branches,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: commit.id)
-
- expect(assigns(:branches)).to include("master", "feature_conflict")
- expect(assigns(:tags)).to include("v1.1.0")
- end
- end
-
- describe '#revert' do
- context 'when target branch is not provided' do
- it 'should render the 404 page' do
- post(:revert,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: commit.id)
-
- expect(response).not_to be_success
- expect(response.status).to eq(404)
- end
- end
-
- context 'when the revert was successful' do
- it 'should redirect to the commits page' do
- post(:revert,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- target_branch: 'master',
- id: commit.id)
-
- expect(response).to redirect_to namespace_project_commits_path(project.namespace, project, 'master')
- expect(flash[:notice]).to eq('The commit has been successfully reverted.')
- end
- end
-
- context 'when the revert failed' do
- before do
- post(:revert,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- target_branch: 'master',
- id: commit.id)
- end
-
- it 'should redirect to the commit page' do
- # Reverting a commit that has been already reverted.
- post(:revert,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- target_branch: 'master',
- id: commit.id)
-
- expect(response).to redirect_to namespace_project_commit_path(project.namespace, project, commit.id)
- expect(flash[:alert]).to match('Sorry, we cannot revert this commit automatically.')
- end
- end
- end
-
- describe '#cherry_pick' do
- context 'when target branch is not provided' do
- it 'should render the 404 page' do
- post(:cherry_pick,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: master_pickable_commit.id)
-
- expect(response).not_to be_success
- expect(response.status).to eq(404)
- end
- end
-
- context 'when the cherry-pick was successful' do
- it 'should redirect to the commits page' do
- post(:cherry_pick,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- target_branch: 'master',
- id: master_pickable_commit.id)
-
- expect(response).to redirect_to namespace_project_commits_path(project.namespace, project, 'master')
- expect(flash[:notice]).to eq('The commit has been successfully cherry-picked.')
- end
- end
-
- context 'when the cherry_pick failed' do
- before do
- post(:cherry_pick,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- target_branch: 'master',
- id: master_pickable_commit.id)
- end
-
- it 'should redirect to the commit page' do
- # Cherry-picking a commit that has been already cherry-picked.
- post(:cherry_pick,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- target_branch: 'master',
- id: master_pickable_commit.id)
-
- expect(response).to redirect_to namespace_project_commit_path(project.namespace, project, master_pickable_commit.id)
- expect(flash[:alert]).to match('Sorry, we cannot cherry-pick this commit automatically.')
- end
- end
- end
-end
diff --git a/spec/controllers/groups/group_members_controller_spec.rb b/spec/controllers/groups/group_members_controller_spec.rb
index 89c2c26a367..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,9 +118,7 @@ describe Groups::GroupMembersController do
it 'cannot removes himself from the group' do
delete :leave, group_id: group
- expect(response).to redirect_to(group_path(group))
- expect(response).to set_flash[:alert].to "You can not leave the \"#{group.name}\" group. Transfer or delete the group."
- expect(group.users).to include user
+ expect(response).to have_http_status(403)
end
end
@@ -134,8 +132,8 @@ describe Groups::GroupMembersController do
delete :leave, group_id: group
expect(response).to set_flash.to 'Your access request to the group has been withdrawn.'
- expect(response).to redirect_to(dashboard_groups_path)
- expect(group.members.request).to be_empty
+ expect(response).to redirect_to(group_path(group))
+ expect(group.requesters).to be_empty
expect(group.users).not_to include user
end
end
@@ -155,7 +153,7 @@ describe Groups::GroupMembersController do
expect(response).to set_flash.to 'Your request for access has been queued for review.'
expect(response).to redirect_to(group_path(group))
- expect(group.members.request.exists?(user_id: user)).to be_truthy
+ expect(group.requesters.exists?(user_id: user)).to be_truthy
expect(group.users).not_to include user
end
end
@@ -168,7 +166,7 @@ describe Groups::GroupMembersController do
post :approve_access_request, group_id: group,
id: 42
- expect(response.status).to eq(403)
+ expect(response).to have_http_status(403)
end
end
@@ -177,7 +175,7 @@ describe Groups::GroupMembersController do
let(:group_requester) { create(:user) }
let(:member) do
group.request_access(group_requester)
- group.members.request.find_by(user_id: group_requester)
+ group.requesters.find_by(user_id: group_requester)
end
context 'when user does not have enough rights' do
@@ -190,7 +188,7 @@ describe Groups::GroupMembersController do
post :approve_access_request, group_id: group,
id: member
- expect(response.status).to eq(403)
+ expect(response).to have_http_status(403)
expect(group.users).not_to include group_requester
end
end
diff --git a/spec/controllers/health_check_controller_spec.rb b/spec/controllers/health_check_controller_spec.rb
index 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/help_controller_spec.rb b/spec/controllers/help_controller_spec.rb
index 1f7fd517342..347bef1e129 100644
--- a/spec/controllers/help_controller_spec.rb
+++ b/spec/controllers/help_controller_spec.rb
@@ -11,7 +11,7 @@ describe HelpController do
context 'for Markdown formats' do
context 'when requested file exists' do
before do
- get :show, category: 'ssh', file: 'README', format: :md
+ get :show, path: 'ssh/README', format: :md
end
it 'assigns to @markdown' do
@@ -26,7 +26,7 @@ describe HelpController do
context 'when requested file is missing' do
it 'renders not found' do
- get :show, category: 'foo', file: 'bar', format: :md
+ get :show, path: 'foo/bar', format: :md
expect(response).to be_not_found
end
end
@@ -36,8 +36,7 @@ describe HelpController do
context 'when requested file exists' do
it 'renders the raw file' do
get :show,
- category: 'workflow/protected_branches',
- file: 'protected_branches1',
+ path: 'workflow/protected_branches/protected_branches1',
format: :png
expect(response).to be_success
expect(response.content_type).to eq 'image/png'
@@ -48,8 +47,7 @@ describe HelpController do
context 'when requested file is missing' do
it 'renders not found' do
get :show,
- category: 'foo',
- file: 'bar',
+ path: 'foo/bar',
format: :png
expect(response).to be_not_found
end
@@ -59,11 +57,19 @@ describe HelpController do
context 'for other formats' do
it 'always renders not found' do
get :show,
- category: 'ssh',
- file: 'README',
+ path: 'ssh/README',
format: :foo
expect(response).to be_not_found
end
end
end
+
+ describe 'GET #ui' do
+ context 'for UI Development Kit' do
+ it 'renders found' do
+ get :ui
+ expect(response).to have_http_status(200)
+ end
+ end
+ end
end
diff --git a/spec/controllers/import/github_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb
index 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/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb
new file mode 100644
index 00000000000..9444a50b1ce
--- /dev/null
+++ b/spec/controllers/projects/blob_controller_spec.rb
@@ -0,0 +1,40 @@
+require 'rails_helper'
+
+describe Projects::BlobController do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ before do
+ user = create(:user)
+ project.team << [user, :master]
+
+ sign_in(user)
+ end
+
+ describe 'GET diff' do
+ render_views
+
+ def do_get(opts = {})
+ params = { namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: 'master/CHANGELOG' }
+ get :diff, params.merge(opts)
+ end
+
+ context 'when essential params are missing' do
+ it 'renders nothing' do
+ do_get
+
+ expect(response.body).to be_blank
+ end
+ end
+
+ context 'when essential params are present' do
+ it 'renders the diff content' do
+ do_get(since: 1, to: 5, offset: 10)
+
+ expect(response.body).to be_present
+ end
+ end
+ end
+end
diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb
index 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/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb
index 6e3db10e451..3001d32e719 100644
--- a/spec/controllers/projects/commit_controller_spec.rb
+++ b/spec/controllers/projects/commit_controller_spec.rb
@@ -1,9 +1,29 @@
-require 'rails_helper'
+require 'spec_helper'
describe Projects::CommitController do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:commit) { project.commit("master") }
+ let(:master_pickable_sha) { '7d3b0f7cff5f37573aea97cebfd5692ea1689924' }
+ let(:master_pickable_commit) { project.commit(master_pickable_sha) }
+
+ before do
+ sign_in(user)
+ project.team << [user, :master]
+ end
+
describe 'GET show' do
render_views
+ def go(extra_params = {})
+ params = {
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param
+ }
+
+ get :show, params.merge(extra_params)
+ end
+
let(:project) { create(:project) }
before do
@@ -15,7 +35,7 @@ describe Projects::CommitController do
context 'with valid id' do
it 'responds with 200' do
- go id: project.commit.id
+ go(id: commit.id)
expect(response).to be_ok
end
@@ -23,27 +43,274 @@ describe Projects::CommitController do
context 'with invalid id' do
it 'responds with 404' do
- go id: project.commit.id.reverse
+ go(id: commit.id.reverse)
expect(response).to be_not_found
end
end
it 'handles binary files' do
- get(:show,
+ go(id: TestEnv::BRANCH_SHA['binary-encoding'], format: 'html')
+
+ expect(response).to be_success
+ end
+
+ shared_examples "export as" do |format|
+ it "should generally work" do
+ go(id: commit.id, format: format)
+
+ expect(response).to be_success
+ end
+
+ it "should generate it" do
+ expect_any_instance_of(Commit).to receive(:"to_#{format}")
+
+ go(id: commit.id, format: format)
+ end
+
+ it "should render it" do
+ go(id: commit.id, format: format)
+
+ expect(response.body).to eq(commit.send(:"to_#{format}"))
+ end
+
+ it "should not escape Html" do
+ allow_any_instance_of(Commit).to receive(:"to_#{format}").
+ and_return('HTML entities &<>" ')
+
+ go(id: commit.id, format: format)
+
+ expect(response.body).not_to include('&amp;')
+ expect(response.body).not_to include('&gt;')
+ expect(response.body).not_to include('&lt;')
+ expect(response.body).not_to include('&quot;')
+ end
+ end
+
+ describe "as diff" do
+ include_examples "export as", :diff
+ let(:format) { :diff }
+
+ it "should really only be a git diff" do
+ go(id: commit.id, format: format)
+
+ expect(response.body).to start_with("diff --git")
+ end
+
+ it "should really only be a git diff without whitespace changes" do
+ go(id: '66eceea0db202bb39c4e445e8ca28689645366c5', format: format, w: 1)
+
+ expect(response.body).to start_with("diff --git")
+ # without whitespace option, there are more than 2 diff_splits
+ diff_splits = assigns(:diffs).first.diff.split("\n")
+ expect(diff_splits.length).to be <= 2
+ end
+ end
+
+ describe "as patch" do
+ include_examples "export as", :patch
+ let(:format) { :patch }
+
+ it "should really be a git email patch" do
+ go(id: commit.id, format: format)
+
+ expect(response.body).to start_with("From #{commit.id}")
+ end
+
+ it "should contain a git diff" do
+ go(id: commit.id, format: format)
+
+ expect(response.body).to match(/^diff --git/)
+ end
+ end
+
+ context 'commit that removes a submodule' do
+ render_views
+
+ let(:fork_project) { create(:forked_project_with_submodules, visibility_level: 20) }
+ let(:commit) { fork_project.commit('remove-submodule') }
+
+ it 'renders it' do
+ get(:show,
+ namespace_id: fork_project.namespace.to_param,
+ project_id: fork_project.to_param,
+ id: commit.id)
+
+ expect(response).to be_success
+ end
+ end
+ end
+
+ describe "GET branches" do
+ it "contains branch and tags information" do
+ get(:branches,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
- id: TestEnv::BRANCH_SHA['binary-encoding'],
- format: "html")
+ id: commit.id)
- expect(response).to be_success
+ expect(assigns(:branches)).to include("master", "feature_conflict")
+ expect(assigns(:tags)).to include("v1.1.0")
+ end
+ end
+
+ describe 'POST revert' do
+ context 'when target branch is not provided' do
+ it 'should render the 404 page' do
+ post(:revert,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: commit.id)
+
+ expect(response).not_to be_success
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ context 'when the revert was successful' do
+ it 'should redirect to the commits page' do
+ post(:revert,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ target_branch: 'master',
+ id: commit.id)
+
+ expect(response).to redirect_to namespace_project_commits_path(project.namespace, project, 'master')
+ expect(flash[:notice]).to eq('The commit has been successfully reverted.')
+ end
+ end
+
+ context 'when the revert failed' do
+ before do
+ post(:revert,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ target_branch: 'master',
+ id: commit.id)
+ end
+
+ it 'should redirect to the commit page' do
+ # Reverting a commit that has been already reverted.
+ post(:revert,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ target_branch: 'master',
+ id: commit.id)
+
+ expect(response).to redirect_to namespace_project_commit_path(project.namespace, project, commit.id)
+ expect(flash[:alert]).to match('Sorry, we cannot revert this commit automatically.')
+ end
+ end
+ end
+
+ describe 'POST cherry_pick' do
+ context 'when target branch is not provided' do
+ it 'should render the 404 page' do
+ post(:cherry_pick,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: master_pickable_commit.id)
+
+ expect(response).not_to be_success
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ context 'when the cherry-pick was successful' do
+ it 'should redirect to the commits page' do
+ post(:cherry_pick,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ target_branch: 'master',
+ id: master_pickable_commit.id)
+
+ expect(response).to redirect_to namespace_project_commits_path(project.namespace, project, 'master')
+ expect(flash[:notice]).to eq('The commit has been successfully cherry-picked.')
+ end
end
- def go(id:)
- get :show,
+ context 'when the cherry_pick failed' do
+ before do
+ post(:cherry_pick,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ target_branch: 'master',
+ id: master_pickable_commit.id)
+ end
+
+ it 'should redirect to the commit page' do
+ # Cherry-picking a commit that has been already cherry-picked.
+ post(:cherry_pick,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ target_branch: 'master',
+ id: master_pickable_commit.id)
+
+ expect(response).to redirect_to namespace_project_commit_path(project.namespace, project, master_pickable_commit.id)
+ expect(flash[:alert]).to match('Sorry, we cannot cherry-pick this commit automatically.')
+ end
+ end
+ end
+
+ describe 'GET diff_for_path' do
+ def diff_for_path(extra_params = {})
+ params = {
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: id
+ project_id: project.to_param
+ }
+
+ get :diff_for_path, params.merge(extra_params)
+ end
+
+ let(:existing_path) { '.gitmodules' }
+
+ context 'when the commit exists' do
+ context 'when the user has access to the project' do
+ context 'when the path exists in the diff' do
+ it 'enables diff notes' do
+ diff_for_path(id: commit.id, old_path: existing_path, new_path: existing_path)
+
+ expect(assigns(:diff_notes_disabled)).to be_falsey
+ expect(assigns(:comments_target)).to eq(noteable_type: 'Commit',
+ commit_id: commit.id)
+ end
+
+ it 'only renders the diffs for the path given' do
+ expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs, diff_refs, project|
+ expect(diffs.map(&:new_path)).to contain_exactly(existing_path)
+ meth.call(diffs, diff_refs, project)
+ end
+
+ diff_for_path(id: commit.id, old_path: existing_path, new_path: existing_path)
+ end
+ end
+
+ context 'when the path does not exist in the diff' do
+ before { diff_for_path(id: commit.id, old_path: existing_path.succ, new_path: existing_path.succ) }
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ context 'when the user does not have access to the project' do
+ before do
+ project.team.truncate
+ diff_for_path(id: commit.id, old_path: existing_path, new_path: existing_path)
+ end
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ context 'when the commit does not exist' do
+ before { diff_for_path(id: commit.id.succ, old_path: existing_path, new_path: existing_path) }
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
end
end
end
diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb
index 4018dac95a2..4058d5e2453 100644
--- a/spec/controllers/projects/compare_controller_spec.rb
+++ b/spec/controllers/projects/compare_controller_spec.rb
@@ -64,4 +64,73 @@ describe Projects::CompareController do
expect(assigns(:commits)).to eq(nil)
end
end
+
+ describe 'GET diff_for_path' do
+ def diff_for_path(extra_params = {})
+ params = {
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param
+ }
+
+ get :diff_for_path, params.merge(extra_params)
+ end
+
+ let(:existing_path) { 'files/ruby/feature.rb' }
+
+ context 'when the from and to refs exist' do
+ context 'when the user has access to the project' do
+ context 'when the path exists in the diff' do
+ it 'disables diff notes' do
+ diff_for_path(from: ref_from, to: ref_to, old_path: existing_path, new_path: existing_path)
+
+ expect(assigns(:diff_notes_disabled)).to be_truthy
+ end
+
+ it 'only renders the diffs for the path given' do
+ expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs, diff_refs, project|
+ expect(diffs.map(&:new_path)).to contain_exactly(existing_path)
+ meth.call(diffs, diff_refs, project)
+ end
+
+ diff_for_path(from: ref_from, to: ref_to, old_path: existing_path, new_path: existing_path)
+ end
+ end
+
+ context 'when the path does not exist in the diff' do
+ before { diff_for_path(from: ref_from, to: ref_to, old_path: existing_path.succ, new_path: existing_path.succ) }
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ context 'when the user does not have access to the project' do
+ before do
+ project.team.truncate
+ diff_for_path(from: ref_from, to: ref_to, old_path: existing_path, new_path: existing_path)
+ end
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ context 'when the from ref does not exist' do
+ before { diff_for_path(from: ref_from.succ, to: ref_to, old_path: existing_path, new_path: existing_path) }
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ context 'when the to ref does not exist' do
+ before { diff_for_path(from: ref_from, to: ref_to.succ, old_path: existing_path, new_path: existing_path) }
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
end
diff --git a/spec/controllers/projects/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 4b408c03703..210085e3b1a 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -10,7 +10,7 @@ describe Projects::MergeRequestsController do
project.team << [user, :master]
end
- describe '#new' do
+ describe 'GET new' do
context 'merge request that removes a submodule' do
render_views
@@ -34,7 +34,7 @@ describe Projects::MergeRequestsController do
end
end
- describe "#show" do
+ describe "GET show" do
shared_examples "export merge as" do |format|
it "should generally work" do
get(:show,
@@ -96,31 +96,19 @@ describe Projects::MergeRequestsController do
end
describe "as patch" do
- include_examples "export merge as", :patch
- let(:format) { :patch }
-
- it "should really be a git email patch with commit" do
- get(:show,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: merge_request.iid, format: format)
-
- expect(response.body[0..100]).to start_with("From #{merge_request.commits.last.id}")
- end
-
- it "should contain git diffs" do
+ it 'triggers workhorse to serve the request' do
get(:show,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: merge_request.iid,
- format: format)
+ format: :patch)
- expect(response.body).to match(/^diff --git/)
+ expect(response.headers[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-format-patch:")
end
end
end
- describe 'GET #index' do
+ describe 'GET index' do
def get_merge_requests
get :index,
namespace_id: project.namespace.to_param,
@@ -129,7 +117,6 @@ describe Projects::MergeRequestsController do
end
context 'when filtering by opened state' do
-
context 'with opened merge requests' do
it 'should list those merge requests' do
get_merge_requests
@@ -150,11 +137,10 @@ describe Projects::MergeRequestsController do
expect(assigns(:merge_requests)).to include(merge_request)
end
end
-
end
end
- describe 'PUT #update' do
+ describe 'PUT update' do
context 'there is no source project' do
let(:project) { create(:project) }
let(:fork_project) { create(:forked_project_with_submodules) }
@@ -182,7 +168,7 @@ describe Projects::MergeRequestsController do
end
end
- describe 'POST #merge' do
+ describe 'POST merge' do
let(:base_params) do
{
namespace_id: project.namespace.path,
@@ -226,7 +212,7 @@ describe Projects::MergeRequestsController do
context 'when the sha parameter matches the source SHA' do
def merge_with_sha
- post :merge, base_params.merge(sha: merge_request.source_sha)
+ post :merge, base_params.merge(sha: merge_request.diff_head_sha)
end
it 'returns :success' do
@@ -243,11 +229,11 @@ describe Projects::MergeRequestsController do
context 'when merge_when_build_succeeds is passed' do
def merge_when_build_succeeds
- post :merge, base_params.merge(sha: merge_request.source_sha, merge_when_build_succeeds: '1')
+ post :merge, base_params.merge(sha: merge_request.diff_head_sha, merge_when_build_succeeds: '1')
end
before do
- create(:ci_empty_pipeline, project: project, sha: merge_request.source_sha, ref: merge_request.source_branch)
+ create(:ci_empty_pipeline, project: project, sha: merge_request.diff_head_sha, ref: merge_request.source_branch)
end
it 'returns :merge_when_build_succeeds' do
@@ -264,15 +250,27 @@ describe Projects::MergeRequestsController do
merge_when_build_succeeds
end
+
+ context 'when project.only_allow_merge_if_build_succeeds? is true' do
+ before do
+ project.update_column(:only_allow_merge_if_build_succeeds, true)
+ end
+
+ it 'returns :merge_when_build_succeeds' do
+ merge_when_build_succeeds
+
+ expect(assigns(:status)).to eq(:merge_when_build_succeeds)
+ end
+ end
end
end
end
- describe "DELETE #destroy" do
+ describe "DELETE destroy" do
it "denies access to users unless they're admin or project owner" do
delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: merge_request.iid
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
context "when the user is owner" do
@@ -285,103 +283,217 @@ describe Projects::MergeRequestsController do
it "deletes the merge request" do
delete :destroy, namespace_id: project.namespace.path, project_id: project.path, id: merge_request.iid
- expect(response.status).to eq(302)
+ expect(response).to have_http_status(302)
expect(controller).to set_flash[:notice].to(/The merge request was successfully deleted\./).now
end
end
end
describe 'GET diffs' do
- def go(format: 'html')
- get :diffs,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: merge_request.iid,
- format: format
+ def go(extra_params = {})
+ params = {
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: merge_request.iid
+ }
+
+ get :diffs, params.merge(extra_params)
end
- context 'as html' do
- it 'renders the diff template' do
- go
+ context 'with default params' do
+ context 'as html' do
+ before { go(format: 'html') }
- expect(response).to render_template('diffs')
+ it 'renders the diff template' do
+ expect(response).to render_template('diffs')
+ end
end
- end
- context 'as json' do
- it 'renders the diffs template to a string' do
- go format: 'json'
+ context 'as json' do
+ before { go(format: 'json') }
- expect(response).to render_template('projects/merge_requests/show/_diffs')
- expect(JSON.parse(response.body)).to have_key('html')
+ it 'renders the diffs template to a string' do
+ expect(response).to render_template('projects/merge_requests/show/_diffs')
+ expect(JSON.parse(response.body)).to have_key('html')
+ end
end
- end
- context 'with forked projects with submodules' do
- render_views
+ context 'with forked projects with submodules' do
+ render_views
- let(:project) { create(:project) }
- let(:fork_project) { create(:forked_project_with_submodules) }
- let(:merge_request) { create(:merge_request_with_diffs, source_project: fork_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) }
+ let(:project) { create(:project) }
+ let(:fork_project) { create(:forked_project_with_submodules) }
+ let(:merge_request) { create(:merge_request_with_diffs, source_project: fork_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) }
- before do
- fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
- fork_project.save
- merge_request.reload
- end
-
- it 'renders' do
- go format: 'json'
+ before do
+ fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
+ fork_project.save
+ merge_request.reload
+ go(format: 'json')
+ end
- expect(response).to be_success
- expect(response.body).to have_content('Subproject commit')
+ it 'renders' do
+ expect(response).to be_success
+ expect(response.body).to have_content('Subproject commit')
+ end
end
end
- end
- describe 'GET diffs with ignore_whitespace_change' do
- def go(format: 'html')
- get :diffs,
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: merge_request.iid,
- format: format,
- w: 1
- end
+ context 'with ignore_whitespace_change' do
+ context 'as html' do
+ before { go(format: 'html', w: 1) }
- context 'as html' do
- it 'renders the diff template' do
- go
+ it 'renders the diff template' do
+ expect(response).to render_template('diffs')
+ end
+ end
- expect(response).to render_template('diffs')
+ context 'as json' do
+ before { go(format: 'json', w: 1) }
+
+ it 'renders the diffs template to a string' do
+ expect(response).to render_template('projects/merge_requests/show/_diffs')
+ expect(JSON.parse(response.body)).to have_key('html')
+ end
end
end
- context 'as json' do
- it 'renders the diffs template to a string' do
- go format: 'json'
+ context 'with view' do
+ before { go(view: 'parallel') }
- expect(response).to render_template('projects/merge_requests/show/_diffs')
- expect(JSON.parse(response.body)).to have_key('html')
+ it 'saves the preferred diff view in a cookie' do
+ expect(response.cookies['diff_view']).to eq('parallel')
end
end
end
- describe 'GET diffs with view' do
- def go(extra_params = {})
+ describe 'GET diff_for_path' do
+ def diff_for_path(extra_params = {})
params = {
namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: merge_request.iid
+ project_id: project.to_param
}
- get :diffs, params.merge(extra_params)
+ get :diff_for_path, params.merge(extra_params)
end
- it 'saves the preferred diff view in a cookie' do
- go view: 'parallel'
+ context 'when an ID param is passed' do
+ let(:existing_path) { 'files/ruby/popen.rb' }
+
+ context 'when the merge request exists' do
+ context 'when the user can view the merge request' do
+ context 'when the path exists in the diff' do
+ it 'enables diff notes' do
+ diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path)
+
+ expect(assigns(:diff_notes_disabled)).to be_falsey
+ expect(assigns(:comments_target)).to eq(noteable_type: 'MergeRequest',
+ noteable_id: merge_request.id)
+ end
+
+ it 'only renders the diffs for the path given' do
+ expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs, diff_refs, project|
+ expect(diffs.map(&:new_path)).to contain_exactly(existing_path)
+ meth.call(diffs, diff_refs, project)
+ end
+
+ diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path)
+ end
+ end
+
+ context 'when the path does not exist in the diff' do
+ before { diff_for_path(id: merge_request.iid, old_path: 'files/ruby/nopen.rb', new_path: 'files/ruby/nopen.rb') }
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ context 'when the user cannot view the merge request' do
+ before do
+ project.team.truncate
+ diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path)
+ end
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ context 'when the merge request does not exist' do
+ before { diff_for_path(id: merge_request.iid.succ, old_path: existing_path, new_path: existing_path) }
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ context 'when the merge request belongs to a different project' do
+ let(:other_project) { create(:empty_project) }
- expect(response.cookies['diff_view']).to eq('parallel')
+ before do
+ other_project.team << [user, :master]
+ diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path, project_id: other_project.to_param)
+ end
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ context 'when source and target params are passed' do
+ let(:existing_path) { 'files/ruby/feature.rb' }
+
+ context 'when both branches are in the same project' do
+ it 'disables diff notes' do
+ diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_branch: 'feature', target_branch: 'master' })
+
+ expect(assigns(:diff_notes_disabled)).to be_truthy
+ end
+
+ it 'only renders the diffs for the path given' do
+ expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs, diff_refs, project|
+ expect(diffs.map(&:new_path)).to contain_exactly(existing_path)
+ meth.call(diffs, diff_refs, project)
+ end
+
+ diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_branch: 'feature', target_branch: 'master' })
+ end
+ end
+
+ context 'when the source branch is in a different project to the target' do
+ let(:other_project) { create(:project) }
+
+ before { other_project.team << [user, :master] }
+
+ context 'when the path exists in the diff' do
+ it 'disables diff notes' do
+ diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' })
+
+ expect(assigns(:diff_notes_disabled)).to be_truthy
+ end
+
+ it 'only renders the diffs for the path given' do
+ expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs, diff_refs, project|
+ expect(diffs.map(&:new_path)).to contain_exactly(existing_path)
+ meth.call(diffs, diff_refs, project)
+ end
+
+ diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' })
+ end
+ end
+
+ context 'when the path does not exist in the diff' do
+ before { diff_for_path(old_path: 'files/ruby/nopen.rb', new_path: 'files/ruby/nopen.rb', merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' }) }
+
+ it 'returns a 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
end
end
diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb
index 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 fc5f458e795..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,11 +171,7 @@ describe Projects::ProjectMembersController do
delete :leave, namespace_id: project.namespace,
project_id: project
- expect(response).to redirect_to(
- namespace_project_path(project.namespace, project)
- )
- expect(response).to set_flash[:alert].to "You can not leave the \"#{project.human_name}\" project. Transfer or delete the project."
- expect(project.users).to include user
+ expect(response).to have_http_status(403)
end
end
@@ -190,8 +186,8 @@ describe Projects::ProjectMembersController do
project_id: project
expect(response).to set_flash.to 'Your access request to the project has been withdrawn.'
- expect(response).to redirect_to(dashboard_projects_path)
- expect(project.members.request).to be_empty
+ expect(response).to redirect_to(namespace_project_path(project.namespace, project))
+ expect(project.requesters).to be_empty
expect(project.users).not_to include user
end
end
@@ -214,7 +210,7 @@ describe Projects::ProjectMembersController do
expect(response).to redirect_to(
namespace_project_path(project.namespace, project)
)
- expect(project.members.request.exists?(user_id: user)).to be_truthy
+ expect(project.requesters.exists?(user_id: user)).to be_truthy
expect(project.users).not_to include user
end
end
@@ -228,7 +224,7 @@ describe Projects::ProjectMembersController do
project_id: project,
id: 42
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
end
@@ -237,7 +233,7 @@ describe Projects::ProjectMembersController do
let(:team_requester) { create(:user) }
let(:member) do
project.request_access(team_requester)
- project.members.request.find_by(user_id: team_requester.id)
+ project.requesters.find_by(user_id: team_requester.id)
end
context 'when user does not have enough rights' do
@@ -251,7 +247,7 @@ describe Projects::ProjectMembersController do
project_id: project,
id: member
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
expect(project.users).not_to include team_requester
end
end
diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb
index 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..936320a3709 100644
--- a/spec/controllers/projects/todo_controller_spec.rb
+++ b/spec/controllers/projects/todo_controller_spec.rb
@@ -1,6 +1,8 @@
require('spec_helper')
describe Projects::TodosController do
+ include ApiHelpers
+
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:issue) { create(:issue, project: project) }
@@ -8,46 +10,54 @@ describe Projects::TodosController do
context 'Issues' do
describe 'POST create' do
+ def go
+ post :create,
+ namespace_id: project.namespace.path,
+ project_id: project.path,
+ issuable_id: issue.id,
+ issuable_type: 'issue',
+ format: 'html'
+ end
+
context 'when authorized' do
before do
sign_in(user)
project.team << [user, :developer]
end
- it 'should create todo for issue' do
+ it 'creates todo for issue' do
expect do
- post(:create, namespace_id: project.namespace.path,
- project_id: project.path,
- issuable_id: issue.id,
- issuable_type: 'issue')
+ go
end.to change { user.todos.count }.by(1)
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
+ end
+
+ it 'returns todo path and pending count' do
+ go
+
+ expect(response).to have_http_status(200)
+ expect(json_response['count']).to eq 1
+ expect(json_response['delete_path']).to match(/\/dashboard\/todos\/\d{1}/)
end
end
context 'when not authorized' do
- it 'should not create todo for issue that user has no access to' do
+ it 'does not create todo for issue that user has no access to' do
sign_in(user)
expect do
- post(:create, namespace_id: project.namespace.path,
- project_id: project.path,
- issuable_id: issue.id,
- issuable_type: 'issue')
+ go
end.to change { user.todos.count }.by(0)
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
- it 'should not create todo for issue when user not logged in' do
+ it 'does not create todo for issue when user not logged in' do
expect do
- post(:create, namespace_id: project.namespace.path,
- project_id: project.path,
- issuable_id: issue.id,
- issuable_type: 'issue')
+ go
end.to change { user.todos.count }.by(0)
- expect(response.status).to eq(302)
+ expect(response).to have_http_status(302)
end
end
end
@@ -55,46 +65,54 @@ describe Projects::TodosController do
context 'Merge Requests' do
describe 'POST create' do
+ def go
+ post :create,
+ namespace_id: project.namespace.path,
+ project_id: project.path,
+ issuable_id: merge_request.id,
+ issuable_type: 'merge_request',
+ format: 'html'
+ end
+
context 'when authorized' do
before do
sign_in(user)
project.team << [user, :developer]
end
- it 'should create todo for merge request' do
+ it 'creates todo for merge request' do
expect do
- post(:create, namespace_id: project.namespace.path,
- project_id: project.path,
- issuable_id: merge_request.id,
- issuable_type: 'merge_request')
+ go
end.to change { user.todos.count }.by(1)
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
+ end
+
+ it 'returns todo path and pending count' do
+ go
+
+ expect(response).to have_http_status(200)
+ expect(json_response['count']).to eq 1
+ expect(json_response['delete_path']).to match(/\/dashboard\/todos\/\d{1}/)
end
end
context 'when not authorized' do
- it 'should not create todo for merge request user has no access to' do
+ it 'does not create todo for merge request user has no access to' do
sign_in(user)
expect do
- post(:create, namespace_id: project.namespace.path,
- project_id: project.path,
- issuable_id: merge_request.id,
- issuable_type: 'merge_request')
+ go
end.to change { user.todos.count }.by(0)
- expect(response.status).to eq(404)
+ expect(response).to have_http_status(404)
end
- it 'should not create todo for merge request user has no access to' do
+ it 'does not create todo for merge request user has no access to' do
expect do
- post(:create, namespace_id: project.namespace.path,
- project_id: project.path,
- issuable_id: merge_request.id,
- issuable_type: 'merge_request')
+ go
end.to change { user.todos.count }.by(0)
- expect(response.status).to eq(302)
+ expect(response).to have_http_status(302)
end
end
end
diff --git a/spec/controllers/projects/tree_controller_spec.rb b/spec/controllers/projects/tree_controller_spec.rb
index 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 fba545560c7..3edce4d339c 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -43,6 +43,26 @@ describe ProjectsController do
end
end
+ context "project with empty repo" do
+ let(:empty_project) { create(:project_empty_repo, :public) }
+
+ before { sign_in(user) }
+
+ User.project_views.keys.each do |project_view|
+ context "with #{project_view} view set" do
+ before do
+ user.update_attributes(project_view: project_view)
+
+ get :show, namespace_id: empty_project.namespace.path, id: empty_project.path
+ end
+
+ it "renders the empty project view" do
+ expect(response).to render_template('empty')
+ end
+ end
+ end
+ end
+
context "rendering default project view" do
render_views
@@ -77,7 +97,7 @@ describe ProjectsController do
get :show, namespace_id: public_project.namespace.path, id: public_project.path
expect(assigns(:project)).to eq(public_project)
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
@@ -89,19 +109,16 @@ describe ProjectsController do
expect(response).to redirect_to("/#{public_project.path_with_namespace}")
end
-
# MySQL queries are case insensitive by default, so this spec would fail.
if Gitlab::Database.postgresql?
context "when there is also a match with the same casing" do
-
let!(:other_project) { create(:project, :public, namespace: public_project.namespace, path: public_project.path.upcase) }
it "loads the exactly matched project" do
-
get :show, namespace_id: public_project.namespace.path, id: public_project.path.upcase
expect(assigns(:project)).to eq(other_project)
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
end
@@ -146,7 +163,7 @@ describe ProjectsController do
expect(project.repository.path).to include(new_path)
expect(assigns(:repository).path).to eq(project.repository.path)
- expect(response.status).to eq(200)
+ expect(response).to have_http_status(200)
end
end
@@ -161,7 +178,7 @@ describe ProjectsController do
delete :destroy, namespace_id: project.namespace.path, id: project.path
expect { Project.find(orig_id) }.to raise_error(ActiveRecord::RecordNotFound)
- expect(response.status).to eq(302)
+ expect(response).to have_http_status(302)
expect(response).to redirect_to(dashboard_projects_path)
end
end
@@ -234,7 +251,27 @@ 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
+
+ describe "GET refs" do
+ it "should get a list of branches and tags" do
+ get :refs, namespace_id: public_project.namespace.path, id: public_project.path
+
+ parsed_body = JSON.parse(response.body)
+ expect(parsed_body["Branches"]).to include("master")
+ expect(parsed_body["Tags"]).to include("v1.0.0")
+ expect(parsed_body["Commits"]).to be_nil
+ end
+
+ it "should get a list of branches, tags and commits" do
+ get :refs, namespace_id: public_project.namespace.path, id: public_project.path, ref: "123456"
+
+ parsed_body = JSON.parse(response.body)
+ expect(parsed_body["Branches"]).to include("master")
+ expect(parsed_body["Tags"]).to include("v1.0.0")
+ expect(parsed_body["Commits"]).to include("123456")
end
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/ci/builds.rb b/spec/factories/ci/builds.rb
index fe05a0cfc00..5e19e403c6b 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -3,6 +3,8 @@ include ActionDispatch::TestProcess
FactoryGirl.define do
factory :ci_build, class: Ci::Build do
name 'test'
+ stage 'test'
+ stage_idx 0
ref 'master'
tag false
created_at 'Di 29. Okt 09:50:00 CET 2013'
@@ -15,6 +17,11 @@ FactoryGirl.define do
services: ["postgres"]
}
end
+ yaml_variables do
+ [
+ { key: :DB_NAME, value: 'postgres', public: true }
+ ]
+ end
pipeline factory: :ci_pipeline
@@ -38,6 +45,11 @@ FactoryGirl.define do
status 'pending'
end
+ trait :manual do
+ status 'skipped'
+ self.when 'manual'
+ end
+
trait :allowed_to_fail do
allow_failure true
end
diff --git a/spec/factories/ci/commits.rb b/spec/factories/ci/pipelines.rb
index a039bef6f3c..a039bef6f3c 100644
--- a/spec/factories/ci/commits.rb
+++ b/spec/factories/ci/pipelines.rb
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
index 696cf276e57..83e38095feb 100644
--- a/spec/factories/notes.rb
+++ b/spec/factories/notes.rb
@@ -10,21 +10,46 @@ FactoryGirl.define do
on_issue
factory :note_on_commit, traits: [:on_commit]
- factory :note_on_commit_diff, traits: [:on_commit, :on_diff], class: LegacyDiffNote
factory :note_on_issue, traits: [:on_issue], aliases: [:votable_note]
factory :note_on_merge_request, traits: [:on_merge_request]
- factory :note_on_merge_request_diff, traits: [:on_merge_request, :on_diff], class: LegacyDiffNote
factory :note_on_project_snippet, traits: [:on_project_snippet]
factory :system_note, traits: [:system]
+ factory :legacy_diff_note_on_commit, traits: [:on_commit, :legacy_diff_note], class: LegacyDiffNote
+ factory :legacy_diff_note_on_merge_request, traits: [:on_merge_request, :legacy_diff_note], class: LegacyDiffNote
+
+ factory :diff_note_on_merge_request, traits: [:on_merge_request], class: DiffNote do
+ position do
+ Gitlab::Diff::Position.new(
+ old_path: "files/ruby/popen.rb",
+ new_path: "files/ruby/popen.rb",
+ old_line: nil,
+ new_line: 14,
+ diff_refs: noteable.diff_refs
+ )
+ end
+ end
+
+ factory :diff_note_on_commit, traits: [:on_commit], class: DiffNote do
+ position do
+ Gitlab::Diff::Position.new(
+ old_path: "files/ruby/popen.rb",
+ new_path: "files/ruby/popen.rb",
+ old_line: nil,
+ new_line: 14,
+ diff_refs: project.commit(commit_id).try(:diff_refs)
+ )
+ end
+ end
+
trait :on_commit do
noteable nil
- noteable_id nil
noteable_type 'Commit'
+ noteable_id nil
commit_id RepoHelpers.sample_commit.id
end
- trait :on_diff do
+ trait :legacy_diff_note do
line_code "0_184_184"
end
diff --git a/spec/factories/notification_settings.rb b/spec/factories/notification_settings.rb
new file mode 100644
index 00000000000..b5e96d18b8f
--- /dev/null
+++ b/spec/factories/notification_settings.rb
@@ -0,0 +1,8 @@
+FactoryGirl.define do
+ factory :notification_setting do
+ source factory: :empty_project
+ user
+ level 3
+ events []
+ end
+end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 5c8ddbebf0d..b682ced75ac 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -2,7 +2,7 @@ FactoryGirl.define do
# Project without repository
#
# Project does not have bare repository.
- # Use this factory if you dont need repository in tests
+ # Use this factory if you don't need repository in tests
factory :empty_project, class: 'Project' do
sequence(:name) { |n| "project#{n}" }
path { name.downcase.gsub(/\s/, '_') }
diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb
index f426e27afed..866e663f026 100644
--- a/spec/factories/todos.rb
+++ b/spec/factories/todos.rb
@@ -22,5 +22,13 @@ FactoryGirl.define do
trait :build_failed do
action { Todo::BUILD_FAILED }
end
+
+ trait :approval_required do
+ action { Todo::APPROVAL_REQUIRED }
+ end
+
+ trait :done do
+ state :done
+ end
end
end
diff --git a/spec/features/admin/admin_abuse_reports_spec.rb b/spec/features/admin/admin_abuse_reports_spec.rb
new file mode 100644
index 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_builds_spec.rb b/spec/features/admin/admin_builds_spec.rb
index a6198389f04..e177059d959 100644
--- a/spec/features/admin/admin_builds_spec.rb
+++ b/spec/features/admin/admin_builds_spec.rb
@@ -36,12 +36,45 @@ describe 'Admin Builds' do
end
end
+ context 'Pending tab' do
+ context 'when have pending builds' do
+ it 'shows pending builds' do
+ build1 = create(:ci_build, pipeline: pipeline, status: :pending)
+ build2 = create(:ci_build, pipeline: pipeline, status: :running)
+ build3 = create(:ci_build, pipeline: pipeline, status: :success)
+ build4 = create(:ci_build, pipeline: pipeline, status: :failed)
+
+ visit admin_builds_path(scope: :pending)
+
+ expect(page).to have_selector('.nav-links li.active', text: 'Pending')
+ expect(page.find('.build-link')).to have_content(build1.id)
+ expect(page.find('.build-link')).not_to have_content(build2.id)
+ expect(page.find('.build-link')).not_to have_content(build3.id)
+ expect(page.find('.build-link')).not_to have_content(build4.id)
+ expect(page).to have_link 'Cancel all'
+ end
+ end
+
+ context 'when have no builds pending' do
+ it 'shows a message' do
+ create(:ci_build, pipeline: pipeline, status: :success)
+
+ visit admin_builds_path(scope: :pending)
+
+ expect(page).to have_selector('.nav-links li.active', text: 'Pending')
+ expect(page).to have_content 'No builds to show'
+ expect(page).not_to have_link 'Cancel all'
+ end
+ end
+ end
+
context 'Running tab' do
context 'when have running builds' do
it 'shows running builds' do
- build1 = create(:ci_build, pipeline: pipeline, status: :pending)
+ build1 = create(:ci_build, pipeline: pipeline, status: :running)
build2 = create(:ci_build, pipeline: pipeline, status: :success)
build3 = create(:ci_build, pipeline: pipeline, status: :failed)
+ build4 = create(:ci_build, pipeline: pipeline, status: :pending)
visit admin_builds_path(scope: :running)
@@ -49,6 +82,7 @@ describe 'Admin Builds' do
expect(page.find('.build-link')).to have_content(build1.id)
expect(page.find('.build-link')).not_to have_content(build2.id)
expect(page.find('.build-link')).not_to have_content(build3.id)
+ expect(page.find('.build-link')).not_to have_content(build4.id)
expect(page).to have_link 'Cancel all'
end
end
diff --git a/spec/features/admin/admin_disables_git_access_protocol_spec.rb b/spec/features/admin/admin_disables_git_access_protocol_spec.rb
new file mode 100644
index 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 9499cd4e025..2f82fafc13a 100644
--- a/spec/features/admin/admin_runners_spec.rb
+++ b/spec/features/admin/admin_runners_spec.rb
@@ -60,6 +60,66 @@ describe "Admin Runners" do
it { expect(page).to have_content(@project1.name_with_namespace) }
it { expect(page).not_to have_content(@project2.name_with_namespace) }
end
+
+ describe 'enable/create' do
+ 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
+
+ context 'with specific runner' do
+ before do
+ @project1.runners << runner
+ visit admin_runner_path(runner)
+ end
+
+ 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
+
+ it_behaves_like 'assignable runner'
+ end
+ end
+
+ describe 'disable/destroy' do
+ before do
+ @project1.runners << runner
+ visit admin_runner_path(runner)
+ end
+
+ it 'enables specific runner for project' do
+ within '.assigned-projects' do
+ click_on 'Disable'
+ end
+
+ new_runner_project = page.find('.unassigned-projects')
+
+ expect(new_runner_project).to have_content(@project1.path)
+ end
+ end
end
describe 'runners registration token' do
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/builds_spec.rb b/spec/features/builds_spec.rb
index 16832c297ac..cab3dc1d167 100644
--- a/spec/features/builds_spec.rb
+++ b/spec/features/builds_spec.rb
@@ -13,17 +13,33 @@ describe "Builds" do
end
describe "GET /:project/builds" do
+ context "Pending scope" do
+ before do
+ visit namespace_project_builds_path(@project.namespace, @project, scope: :pending)
+ end
+
+ it "shows Pending tab builds" do
+ expect(page).to have_link 'Cancel running'
+ expect(page).to have_selector('.nav-links li.active', text: 'Pending')
+ expect(page).to have_content @build.short_sha
+ expect(page).to have_content @build.ref
+ expect(page).to have_content @build.name
+ end
+ end
+
context "Running scope" do
before do
@build.run!
visit namespace_project_builds_path(@project.namespace, @project, scope: :running)
end
- it { expect(page).to have_selector('.nav-links li.active', text: 'Running') }
- it { expect(page).to have_link 'Cancel running' }
- it { expect(page).to have_content @build.short_sha }
- it { expect(page).to have_content @build.ref }
- it { expect(page).to have_content @build.name }
+ it "shows Running tab builds" do
+ expect(page).to have_selector('.nav-links li.active', text: 'Running')
+ expect(page).to have_link 'Cancel running'
+ expect(page).to have_content @build.short_sha
+ expect(page).to have_content @build.ref
+ expect(page).to have_content @build.name
+ end
end
context "Finished scope" do
@@ -32,9 +48,11 @@ describe "Builds" do
visit namespace_project_builds_path(@project.namespace, @project, scope: :finished)
end
- it { expect(page).to have_selector('.nav-links li.active', text: 'Finished') }
- it { expect(page).to have_content 'No builds to show' }
- it { expect(page).to have_link 'Cancel running' }
+ it "shows Finished tab builds" do
+ expect(page).to have_selector('.nav-links li.active', text: 'Finished')
+ expect(page).to have_content 'No builds to show'
+ expect(page).to have_link 'Cancel running'
+ end
end
context "All builds" do
@@ -43,11 +61,13 @@ describe "Builds" do
visit namespace_project_builds_path(@project.namespace, @project)
end
- it { expect(page).to have_selector('.nav-links li.active', text: 'All') }
- it { expect(page).to have_content @build.short_sha }
- it { expect(page).to have_content @build.ref }
- it { expect(page).to have_content @build.name }
- it { expect(page).not_to have_link 'Cancel running' }
+ it "shows All tab builds" do
+ expect(page).to have_selector('.nav-links li.active', text: 'All')
+ expect(page).to have_content @build.short_sha
+ expect(page).to have_content @build.ref
+ expect(page).to have_content @build.name
+ expect(page).not_to have_link 'Cancel running'
+ end
end
end
diff --git a/spec/features/compare_spec.rb b/spec/features/compare_spec.rb
new file mode 100644
index 00000000000..c62556948e0
--- /dev/null
+++ b/spec/features/compare_spec.rb
@@ -0,0 +1,42 @@
+require "spec_helper"
+
+describe "Compare", js: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+
+ before do
+ project.team << [user, :master]
+ login_as user
+ visit namespace_project_compare_index_path(project.namespace, project, from: "master", to: "master")
+ end
+
+ describe "branches" do
+ it "should pre-populate fields" do
+ expect(page.find_field("from").value).to eq("master")
+ end
+
+ it "should compare branches" do
+ fill_in "from", with: "fea"
+ find("#from").click
+
+ click_link "feature"
+ expect(page.find_field("from").value).to eq("feature")
+
+ click_button "Compare"
+ expect(page).to have_content "Commits"
+ end
+ end
+
+ describe "tags" do
+ it "should compare tags" do
+ fill_in "from", with: "v1.0"
+ find("#from").click
+
+ click_link "v1.0.0"
+ expect(page.find_field("from").value).to eq("v1.0.0")
+
+ click_button "Compare"
+ expect(page).to have_content "Commits"
+ end
+ end
+end
diff --git a/spec/features/container_registry_spec.rb b/spec/features/container_registry_spec.rb
index 53b4f027117..203e55a36f2 100644
--- a/spec/features/container_registry_spec.rb
+++ b/spec/features/container_registry_spec.rb
@@ -26,7 +26,8 @@ describe "Container Registry" do
end
context 'when there are tags' do
- it { expect(page).to have_content(tag_name)}
+ it { expect(page).to have_content(tag_name) }
+ it { expect(page).to have_content('d7a513a66') }
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/environments_spec.rb b/spec/features/environments_spec.rb
index 40fea5211e9..9c018be14b7 100644
--- a/spec/features/environments_spec.rb
+++ b/spec/features/environments_spec.rb
@@ -13,6 +13,7 @@ feature 'Environments', feature: true do
describe 'when showing environments' do
given!(:environment) { }
given!(:deployment) { }
+ given!(:manual) { }
before do
visit namespace_project_environments_path(project.namespace, project)
@@ -20,7 +21,7 @@ feature 'Environments', feature: true do
context 'without environments' do
scenario 'does show no environments' do
- expect(page).to have_content('No environments to show')
+ expect(page).to have_content('You don\'t have any environments right now.')
end
end
@@ -43,6 +44,24 @@ feature 'Environments', feature: true do
scenario 'does show deployment SHA' do
expect(page).to have_link(deployment.short_sha)
end
+
+ context 'with build and manual actions' do
+ given(:pipeline) { create(:ci_pipeline, project: project) }
+ given(:build) { create(:ci_build, pipeline: pipeline) }
+ given(:deployment) { create(:deployment, environment: environment, deployable: build) }
+ given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production') }
+
+ scenario 'does show a play button' do
+ expect(page).to have_link(manual.name.humanize)
+ end
+
+ scenario 'does allow to play manual action' do
+ expect(manual).to be_skipped
+ expect{ click_link(manual.name.humanize) }.not_to change { Ci::Pipeline.count }
+ expect(page).to have_content(manual.name)
+ expect(manual.reload).to be_pending
+ end
+ end
end
end
@@ -54,6 +73,7 @@ feature 'Environments', feature: true do
describe 'when showing the environment' do
given(:environment) { create(:environment, project: project) }
given!(:deployment) { }
+ given!(:manual) { }
before do
visit namespace_project_environment_path(project.namespace, project, environment)
@@ -61,7 +81,7 @@ feature 'Environments', feature: true do
context 'without deployments' do
scenario 'does show no deployments' do
- expect(page).to have_content('No deployments for')
+ expect(page).to have_content('You don\'t have any deployments right now.')
end
end
@@ -77,7 +97,8 @@ feature 'Environments', feature: true do
end
context 'with build' do
- given(:build) { create(:ci_build, project: project) }
+ given(:pipeline) { create(:ci_pipeline, project: project) }
+ given(:build) { create(:ci_build, pipeline: pipeline) }
given(:deployment) { create(:deployment, environment: environment, deployable: build) }
scenario 'does show build name' do
@@ -87,6 +108,21 @@ feature 'Environments', feature: true do
scenario 'does show retry button' do
expect(page).to have_link('Retry')
end
+
+ context 'with manual action' do
+ given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production') }
+
+ scenario 'does show a play button' do
+ expect(page).to have_link(manual.name.humanize)
+ end
+
+ scenario 'does allow to play manual action' do
+ expect(manual).to be_skipped
+ expect{ click_link(manual.name.humanize) }.not_to change { Ci::Pipeline.count }
+ expect(page).to have_content(manual.name)
+ expect(manual.reload).to be_pending
+ end
+ end
end
end
end
@@ -108,7 +144,7 @@ feature 'Environments', feature: true do
end
scenario 'does create a new pipeline' do
- expect(page).to have_content('production')
+ expect(page).to have_content('Production')
end
end
diff --git a/spec/features/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb
new file mode 100644
index 00000000000..688f68d3cff
--- /dev/null
+++ b/spec/features/expand_collapse_diffs_spec.rb
@@ -0,0 +1,248 @@
+require 'spec_helper'
+
+feature 'Expand and collapse diffs', js: true, feature: true do
+ include WaitForAjax
+
+ let(:branch) { 'expand-collapse-diffs' }
+
+ before do
+ login_as :admin
+ project = create(:project)
+
+ # Ensure that undiffable.md is in .gitattributes
+ project.repository.copy_gitattributes(branch)
+ visit namespace_project_commit_path(project.namespace, project, project.commit(branch))
+ execute_script('window.ajaxUris = []; $(document).ajaxSend(function(event, xhr, settings) { ajaxUris.push(settings.url) });')
+ end
+
+ def file_container(filename)
+ find("[data-blob-diff-path*='#{filename}']")
+ end
+
+ # Use define_method instead of let (which is memoized) so that this just works across a
+ # reload.
+ #
+ files = [
+ 'small_diff.md', 'large_diff.md', 'large_diff_renamed.md', 'undiffable.md',
+ 'too_large.md', 'too_large_image.jpg'
+ ]
+
+ files.each do |file|
+ define_method(file.split('.').first) { file_container(file) }
+ end
+
+ context 'visiting a commit with collapsed diffs' do
+ it 'shows small diffs immediately' do
+ expect(small_diff).to have_selector('.code')
+ expect(small_diff).not_to have_selector('.nothing-here-block')
+ end
+
+ it 'collapses large diffs by default' do
+ expect(large_diff).not_to have_selector('.code')
+ expect(large_diff).to have_selector('.nothing-here-block')
+ end
+
+ it 'collapses large diffs for renamed files by default' do
+ expect(large_diff_renamed).not_to have_selector('.code')
+ expect(large_diff_renamed).to have_selector('.nothing-here-block')
+ expect(large_diff_renamed).to have_selector('.file-title .deletion')
+ expect(large_diff_renamed).to have_selector('.file-title .addition')
+ end
+
+ it 'shows non-renderable diffs as such immediately, regardless of their size' do
+ expect(undiffable).not_to have_selector('.code')
+ expect(undiffable).to have_selector('.nothing-here-block')
+ expect(undiffable).to have_content('gitattributes')
+ end
+
+ it 'does not allow diffs that are larger than the maximum size to be expanded' do
+ expect(too_large).not_to have_selector('.code')
+ expect(too_large).to have_selector('.nothing-here-block')
+ expect(too_large).to have_content('too large')
+ end
+
+ it 'shows image diffs immediately, regardless of their size' do
+ expect(too_large_image).not_to have_selector('.nothing-here-block')
+ expect(too_large_image).to have_selector('.image')
+ end
+
+ context 'expanding a diff for a renamed file' do
+ before do
+ large_diff_renamed.find('.nothing-here-block').click
+ wait_for_ajax
+ end
+
+ it 'shows the old content' do
+ old_line = large_diff_renamed.find('.line_content.old')
+
+ expect(old_line).to have_content('two copies')
+ end
+
+ it 'shows the new content' do
+ new_line = large_diff_renamed.find('.line_content.new', match: :prefer_exact)
+
+ expect(new_line).to have_content('three copies')
+ end
+ end
+
+ context 'expanding a large diff' do
+ before do
+ click_link('large_diff.md')
+ wait_for_ajax
+ end
+
+ it 'makes a request to get the content' do
+ ajax_uris = evaluate_script('ajaxUris')
+
+ expect(ajax_uris).not_to be_empty
+ expect(ajax_uris.first).to include('large_diff.md')
+ end
+
+ it 'shows the diff content' do
+ expect(large_diff).to have_selector('.code')
+ expect(large_diff).not_to have_selector('.nothing-here-block')
+ end
+
+ context 'adding a comment to the expanded diff' do
+ let(:comment_text) { 'A comment' }
+
+ before do
+ large_diff.find('.diff-line-num', match: :prefer_exact).hover
+ large_diff.find('.add-diff-note').click
+ large_diff.find('.note-textarea').send_keys comment_text
+ large_diff.find_button('Comment').click
+ wait_for_ajax
+ end
+
+ it 'adds the comment' do
+ expect(large_diff.find('.notes')).to have_content comment_text
+ end
+
+ context 'reloading the page' do
+ before { refresh }
+
+ it 'collapses the large diff by default' do
+ expect(large_diff).not_to have_selector('.code')
+ expect(large_diff).to have_selector('.nothing-here-block')
+ end
+
+ context 'expanding the diff' do
+ before do
+ click_link('large_diff.md')
+ wait_for_ajax
+ end
+
+ it 'shows the diff content' do
+ expect(large_diff).to have_selector('.code')
+ expect(large_diff).not_to have_selector('.nothing-here-block')
+ end
+
+ it 'shows the diff comment' do
+ expect(large_diff.find('.notes')).to have_content comment_text
+ end
+ end
+ end
+ end
+ end
+
+ context 'collapsing an expanded diff' do
+ before { click_link('small_diff.md') }
+
+ it 'hides the diff content' do
+ expect(small_diff).not_to have_selector('.code')
+ expect(small_diff).to have_selector('.nothing-here-block')
+ end
+
+ context 're-expanding the same diff' do
+ before { click_link('small_diff.md') }
+
+ it 'shows the diff content' do
+ expect(small_diff).to have_selector('.code')
+ expect(small_diff).not_to have_selector('.nothing-here-block')
+ end
+
+ it 'does not make a new HTTP request' do
+ expect(evaluate_script('ajaxUris')).not_to include(a_string_matching('small_diff.md'))
+ end
+ end
+ end
+ end
+
+ context 'visiting a commit without collapsed diffs' do
+ let(:branch) { 'feature' }
+
+ it 'does not show Expand all button' do
+ expect(page).not_to have_link('Expand all')
+ end
+ end
+
+ context 'visiting a commit with more than safe files' do
+ let(:branch) { 'expand-collapse-files' }
+
+ # safe-files -> 100 | safe-lines -> 5000 | commit-files -> 105
+ it 'does collapsing from the safe number of files to the end on small files' do
+ expect(page).to have_link('Expand all')
+
+ expect(page).to have_selector('.diff-content', count: 105)
+ expect(page).to have_selector('.diff-collapsed', count: 5)
+
+ %w(file-95.txt file-96.txt file-97.txt file-98.txt file-99.txt).each do |filename|
+ expect(find("[data-blob-diff-path*='#{filename}']")).to have_selector('.diff-collapsed')
+ end
+ end
+ end
+
+ context 'visiting a commit with more than safe lines' do
+ let(:branch) { 'expand-collapse-lines' }
+
+ # safe-files -> 100 | safe-lines -> 5000 | commit_files -> 8 (each 1250 lines)
+ it 'does collapsing from the safe number of lines to the end' do
+ expect(page).to have_link('Expand all')
+
+ expect(page).to have_selector('.diff-content', count: 6)
+ expect(page).to have_selector('.diff-collapsed', count: 2)
+
+ %w(file-4.txt file-5.txt).each do |filename|
+ expect(find("[data-blob-diff-path*='#{filename}']")).to have_selector('.diff-collapsed')
+ end
+ end
+ end
+
+ context 'expanding all diffs' do
+ before do
+ click_link('Expand all')
+ wait_for_ajax
+ execute_script('window.ajaxUris = []; $(document).ajaxSend(function(event, xhr, settings) { ajaxUris.push(settings.url) });')
+ end
+
+ it 'reloads the page with all diffs expanded' do
+ expect(small_diff).to have_selector('.code')
+ expect(small_diff).not_to have_selector('.nothing-here-block')
+
+ expect(large_diff).to have_selector('.code')
+ expect(large_diff).not_to have_selector('.nothing-here-block')
+ end
+
+ context 'collapsing an expanded diff' do
+ before { click_link('small_diff.md') }
+
+ it 'hides the diff content' do
+ expect(small_diff).not_to have_selector('.code')
+ expect(small_diff).to have_selector('.nothing-here-block')
+ end
+
+ context 're-expanding the same diff' do
+ before { click_link('small_diff.md') }
+
+ it 'shows the diff content' do
+ expect(small_diff).to have_selector('.code')
+ expect(small_diff).not_to have_selector('.nothing-here-block')
+ end
+
+ it 'does not make a new HTTP request' do
+ expect(evaluate_script('ajaxUris')).not_to include(a_string_matching('small_diff.md'))
+ end
+ end
+ end
+ end
+end
diff --git a/spec/features/gitlab_flavored_markdown_spec.rb b/spec/features/gitlab_flavored_markdown_spec.rb
index 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/last_owner_cannot_leave_group_spec.rb b/spec/features/groups/members/last_owner_cannot_leave_group_spec.rb
new file mode 100644
index 00000000000..33bf6d3752f
--- /dev/null
+++ b/spec/features/groups/members/last_owner_cannot_leave_group_spec.rb
@@ -0,0 +1,16 @@
+require 'spec_helper'
+
+feature 'Groups > Members > Last owner cannot leave group', feature: true do
+ let(:owner) { create(:user) }
+ let(:group) { create(:group) }
+
+ background do
+ group.add_owner(owner)
+ login_as(owner)
+ visit group_path(group)
+ end
+
+ scenario 'user does not see a "Leave Group" link' do
+ expect(page).not_to have_content 'Leave Group'
+ end
+end
diff --git a/spec/features/groups/members/member_cannot_request_access_to_his_project_spec.rb b/spec/features/groups/members/member_cannot_request_access_to_his_project_spec.rb
new file mode 100644
index 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/member_leaves_group_spec.rb b/spec/features/groups/members/member_leaves_group_spec.rb
new file mode 100644
index 00000000000..3185ff924b9
--- /dev/null
+++ b/spec/features/groups/members/member_leaves_group_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+feature 'Groups > Members > Member leaves group', feature: true do
+ let(:user) { create(:user) }
+ let(:owner) { create(:user) }
+ let(:group) { create(:group, :public) }
+
+ background do
+ group.add_owner(owner)
+ group.add_developer(user)
+ login_as(user)
+ visit group_path(group)
+ end
+
+ scenario 'user leaves group' do
+ click_link 'Leave Group'
+
+ expect(current_path).to eq(dashboard_groups_path)
+ expect(group.users.exists?(user.id)).to be_falsey
+ end
+end
diff --git a/spec/features/groups/members/owner_manages_access_requests_spec.rb b/spec/features/groups/members/owner_manages_access_requests_spec.rb
index 22525ce530b..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,10 +39,9 @@ feature 'Groups > Members > Owner manages access requests', feature: true do
expect(ActionMailer::Base.deliveries.last.subject).to match "Access to the #{group.name} group was denied"
end
-
def expect_visible_access_request(group, user)
- expect(group.members.request.exists?(user_id: user)).to be_truthy
- expect(page).to have_content "#{group.name} access requests (1)"
+ expect(group.requesters.exists?(user_id: user)).to be_truthy
+ expect(page).to have_content "#{group.name} access requests 1"
expect(page).to have_content user.name
end
end
diff --git a/spec/features/groups/members/user_requests_access_spec.rb b/spec/features/groups/members/user_requests_access_spec.rb
index a878a96b6ee..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,16 +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'
@@ -38,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/groups_spec.rb b/spec/features/groups_spec.rb
index 891df65216d..2d8b59472e8 100644
--- a/spec/features/groups_spec.rb
+++ b/spec/features/groups_spec.rb
@@ -1,14 +1,26 @@
require 'spec_helper'
feature 'Group', feature: true do
+ before do
+ login_as(:admin)
+ end
+
+ describe 'creating a group with space in group path' do
+ it 'renders new group form with validation errors' do
+ visit new_group_path
+ fill_in 'Group path', with: 'space group'
+
+ click_button 'Create group'
+
+ expect(current_path).to eq(groups_path)
+ expect(page).to have_content("Path can contain only letters, digits, '_', '-' and '.'. Cannot start with '-' or end in '.'.")
+ end
+ end
+
describe 'description' do
let(:group) { create(:group) }
let(:path) { group_path(group) }
- before do
- login_as(:admin)
- end
-
it 'parses Markdown' do
group.update_attribute(:description, 'This is **my** group')
visit path
diff --git a/spec/features/help_pages_spec.rb b/spec/features/help_pages_spec.rb
index 8c6b669ce78..1e2306d7f59 100644
--- a/spec/features/help_pages_spec.rb
+++ b/spec/features/help_pages_spec.rb
@@ -6,7 +6,7 @@ describe 'Help Pages', feature: true do
login_as :user
end
it 'replace the variable $your_email with the email of the user' do
- visit help_page_path('ssh', 'README')
+ visit help_page_path('ssh/README')
expect(page).to have_content("ssh-keygen -t rsa -C \"#{@user.email}\"")
end
end
diff --git a/spec/features/issues/bulk_assignment_labels_spec.rb b/spec/features/issues/bulk_assignment_labels_spec.rb
index 7143d0e40f3..afc093cc1f5 100644
--- a/spec/features/issues/bulk_assignment_labels_spec.rb
+++ b/spec/features/issues/bulk_assignment_labels_spec.rb
@@ -10,7 +10,7 @@ feature 'Issues > Labels bulk assignment', feature: true do
let!(:bug) { create(:label, project: project, title: 'bug') }
let!(:feature) { create(:label, project: project, title: 'feature') }
- context 'as a allowed user', js: true do
+ context 'as an allowed user', js: true do
before do
project.team << [user, :master]
@@ -164,6 +164,133 @@ feature 'Issues > Labels bulk assignment', feature: true do
end
end
end
+
+ context 'toggling a milestone' do
+ let!(:milestone) { create(:milestone, project: project, title: 'First Release') }
+
+ context 'setting a milestone' do
+ before do
+ issue1.labels << bug
+ issue2.labels << feature
+ visit namespace_project_issues_path(project.namespace, project)
+ end
+
+ it 'labels are kept' do
+ expect(find("#issue_#{issue1.id}")).to have_content 'bug'
+ expect(find("#issue_#{issue2.id}")).to have_content 'feature'
+
+ check 'check_all_issues'
+ open_milestone_dropdown(['First Release'])
+ update_issues
+
+ expect(find("#issue_#{issue1.id}")).to have_content 'bug'
+ expect(find("#issue_#{issue1.id}")).to have_content 'First Release'
+ expect(find("#issue_#{issue2.id}")).to have_content 'feature'
+ expect(find("#issue_#{issue2.id}")).to have_content 'First Release'
+ end
+ end
+
+ context 'setting a milestone and adding another label' do
+ before do
+ issue1.labels << bug
+
+ visit namespace_project_issues_path(project.namespace, project)
+ end
+
+ it 'existing label is kept and new label is present' do
+ expect(find("#issue_#{issue1.id}")).to have_content 'bug'
+
+ check 'check_all_issues'
+ open_milestone_dropdown ['First Release']
+ open_labels_dropdown ['feature']
+ update_issues
+
+ expect(find("#issue_#{issue1.id}")).to have_content 'bug'
+ expect(find("#issue_#{issue1.id}")).to have_content 'feature'
+ expect(find("#issue_#{issue1.id}")).to have_content 'First Release'
+ expect(find("#issue_#{issue2.id}")).to have_content 'feature'
+ expect(find("#issue_#{issue2.id}")).to have_content 'First Release'
+ end
+ end
+
+ context 'setting a milestone and removing existing label' do
+ before do
+ issue1.labels << bug
+ issue1.labels << feature
+ issue2.labels << feature
+
+ visit namespace_project_issues_path(project.namespace, project)
+ end
+
+ it 'existing label is kept and new label is present' do
+ expect(find("#issue_#{issue1.id}")).to have_content 'bug'
+ expect(find("#issue_#{issue1.id}")).to have_content 'bug'
+ expect(find("#issue_#{issue2.id}")).to have_content 'feature'
+
+ check 'check_all_issues'
+ open_milestone_dropdown ['First Release']
+ unmark_labels_in_dropdown ['feature']
+ update_issues
+
+ expect(find("#issue_#{issue1.id}")).to have_content 'bug'
+ expect(find("#issue_#{issue1.id}")).not_to have_content 'feature'
+ expect(find("#issue_#{issue1.id}")).to have_content 'First Release'
+ expect(find("#issue_#{issue2.id}")).not_to have_content 'feature'
+ expect(find("#issue_#{issue2.id}")).to have_content 'First Release'
+ end
+ end
+
+ context 'unsetting a milestone' do
+ before do
+ issue1.milestone = milestone
+ issue2.milestone = milestone
+ issue1.save
+ issue2.save
+ issue1.labels << bug
+ issue2.labels << feature
+
+ visit namespace_project_issues_path(project.namespace, project)
+ end
+
+ it 'labels are kept' do
+ expect(find("#issue_#{issue1.id}")).to have_content 'bug'
+ expect(find("#issue_#{issue1.id}")).to have_content 'First Release'
+ expect(find("#issue_#{issue2.id}")).to have_content 'feature'
+ expect(find("#issue_#{issue2.id}")).to have_content 'First Release'
+
+ check 'check_all_issues'
+ open_milestone_dropdown(['No Milestone'])
+ update_issues
+
+ expect(find("#issue_#{issue1.id}")).to have_content 'bug'
+ expect(find("#issue_#{issue1.id}")).not_to have_content 'First Release'
+ expect(find("#issue_#{issue2.id}")).to have_content 'feature'
+ expect(find("#issue_#{issue2.id}")).not_to have_content 'First Release'
+ end
+ end
+ end
+
+ context 'toggling checked issues' do
+ before do
+ issue1.labels << bug
+
+ visit namespace_project_issues_path(project.namespace, project)
+ end
+
+ it do
+ expect(find("#issue_#{issue1.id}")).to have_content 'bug'
+
+ check_issue issue1
+ open_labels_dropdown ['feature']
+ uncheck_issue issue1
+ check_issue issue1
+ update_issues
+ sleep 1 # needed
+
+ expect(find("#issue_#{issue1.id}")).to have_content 'bug'
+ expect(find("#issue_#{issue1.id}")).not_to have_content 'feature'
+ end
+ end
end
context 'as a guest' do
@@ -181,6 +308,16 @@ feature 'Issues > Labels bulk assignment', feature: true do
end
end
+ def open_milestone_dropdown(items = [])
+ page.within('.issues_bulk_update') do
+ click_button 'Milestone'
+ wait_for_ajax
+ items.map do |item|
+ click_link item
+ end
+ end
+ end
+
def open_labels_dropdown(items = [], unmark = false)
page.within('.issues_bulk_update') do
click_button 'Label'
@@ -201,12 +338,20 @@ feature 'Issues > Labels bulk assignment', feature: true do
open_labels_dropdown(items, true)
end
- def check_issue(issue)
+ def check_issue(issue, uncheck = false)
page.within('.issues-list') do
- check "selected_issue_#{issue.id}"
+ if uncheck
+ uncheck "selected_issue_#{issue.id}"
+ else
+ check "selected_issue_#{issue.id}"
+ end
end
end
+ def uncheck_issue(issue)
+ check_issue(issue, true)
+ end
+
def update_issues
click_button 'Update issues'
wait_for_ajax
diff --git a/spec/features/issues/filter_issues_spec.rb b/spec/features/issues/filter_issues_spec.rb
index 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 c3cb3379440..d51c9abea19 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -22,7 +22,7 @@ describe 'Issues', feature: true do
before do
visit edit_namespace_project_issue_path(project.namespace, project, issue)
- click_button "Go full screen"
+ find('.js-zen-enter').click
end
it 'should open new issue popup' do
@@ -92,7 +92,7 @@ describe 'Issues', feature: true do
end
context 'on edit form' do
- let(:issue) { create(:issue, author: @user,project: project, due_date: Date.today.at_beginning_of_month.to_s) }
+ let(:issue) { create(:issue, author: @user, project: project, due_date: Date.today.at_beginning_of_month.to_s) }
before do
visit edit_namespace_project_issue_path(project.namespace, project, issue)
@@ -361,7 +361,6 @@ describe 'Issues', feature: true do
let(:issue) { create(:issue, project: project, author: @user, assignee: @user) }
context 'by authorized user' do
-
it 'allows user to select unassigned', js: true do
visit namespace_project_issue_path(project.namespace, project, issue)
@@ -420,7 +419,6 @@ describe 'Issues', feature: true do
end
context 'by unauthorized user' do
-
let(:guest) { create(:user) }
before do
@@ -442,8 +440,6 @@ describe 'Issues', feature: true do
let!(:milestone) { create(:milestone, project: project) }
context 'by authorized user' do
-
-
it 'allows user to select unassigned', js: true do
visit namespace_project_issue_path(project.namespace, project, issue)
diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb
index 72b5ff231f7..58753ff21f6 100644
--- a/spec/features/login_spec.rb
+++ b/spec/features/login_spec.rb
@@ -28,6 +28,11 @@ feature 'Login', feature: true do
end
describe 'with two-factor authentication' do
+ def enter_code(code)
+ fill_in 'Two-Factor Authentication code', with: code
+ click_button 'Verify code'
+ end
+
context 'with valid username/password' do
let(:user) { create(:user, :two_factor) }
@@ -36,11 +41,6 @@ feature 'Login', feature: true do
expect(page).to have_content('Two-Factor Authentication')
end
- def enter_code(code)
- fill_in 'Two-Factor Authentication code', with: code
- click_button 'Verify code'
- end
-
it 'does not show a "You are already signed in." error message' do
enter_code(user.current_otp)
expect(page).not_to have_content('You are already signed in.')
@@ -108,6 +108,39 @@ feature 'Login', feature: true do
end
end
end
+
+ context 'logging in via OAuth' do
+ def saml_config
+ OpenStruct.new(name: 'saml', label: 'saml', args: {
+ assertion_consumer_service_url: 'https://localhost:3443/users/auth/saml/callback',
+ idp_cert_fingerprint: '26:43:2C:47:AF:F0:6B:D0:07:9C:AD:A3:74:FE:5D:94:5F:4E:9E:52',
+ idp_sso_target_url: 'https://idp.example.com/sso/saml',
+ issuer: 'https://localhost:3443/',
+ name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
+ })
+ end
+
+ def stub_omniauth_config(messages)
+ Rails.application.env_config['devise.mapping'] = Devise.mappings[:user]
+ Rails.application.routes.disable_clear_and_finalize = true
+ Rails.application.routes.draw do
+ post '/users/auth/saml' => 'omniauth_callbacks#saml'
+ end
+ allow(Gitlab::OAuth::Provider).to receive_messages(providers: [:saml], config_for: saml_config)
+ allow(Gitlab.config.omniauth).to receive_messages(messages)
+ allow_any_instance_of(Object).to receive(:user_omniauth_authorize_path).with('saml').and_return('/users/auth/saml')
+ end
+
+ it 'should show 2FA prompt after OAuth login' do
+ stub_omniauth_config(enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'], providers: [saml_config])
+ user = create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: 'saml')
+ login_via('saml', user, 'my-uid')
+
+ expect(page).to have_content('Two-Factor Authentication')
+ enter_code(user.current_otp)
+ expect(current_path).to eq root_path
+ end
+ end
end
describe 'without two-factor authentication' do
diff --git a/spec/features/merge_requests/created_from_fork_spec.rb b/spec/features/merge_requests/created_from_fork_spec.rb
index b4d2201c729..f676200ecf3 100644
--- a/spec/features/merge_requests/created_from_fork_spec.rb
+++ b/spec/features/merge_requests/created_from_fork_spec.rb
@@ -30,7 +30,7 @@ feature 'Merge request created from fork' do
given(:pipeline) do
create(:ci_pipeline_with_two_job, project: fork_project,
- sha: merge_request.last_commit.id,
+ sha: merge_request.diff_head_sha,
ref: merge_request.source_branch)
end
diff --git a/spec/features/merge_requests/diffs_spec.rb b/spec/features/merge_requests/diffs_spec.rb
new file mode 100644
index 00000000000..c9a0059645d
--- /dev/null
+++ b/spec/features/merge_requests/diffs_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+feature 'Diffs URL', js: true, feature: true do
+ before do
+ login_as :admin
+ @merge_request = create(:merge_request)
+ @project = @merge_request.source_project
+ end
+
+ context 'when visit with */* as accept header' do
+ before(:each) do
+ page.driver.add_header('Accept', '*/*')
+ end
+
+ it 'renders the notes' do
+ create :note_on_merge_request, project: @project, noteable: @merge_request, note: 'Rebasing with master'
+
+ visit diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
+
+ # Load notes and diff through AJAX
+ expect(page).to have_css('.note-text', visible: false, text: 'Rebasing with master')
+ expect(page).to have_css('.diffs.tab-pane.active')
+ end
+ end
+end
diff --git a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb
index c5e6412d7bf..96f7b8c9932 100644
--- a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb
+++ b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb
@@ -12,7 +12,7 @@ feature 'Merge When Build Succeeds', feature: true, js: true do
end
context "Active build for Merge Request" do
- let!(:pipeline) { create(:ci_pipeline, project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch) }
+ let!(:pipeline) { create(:ci_pipeline, project: project, sha: merge_request.diff_head_sha, ref: merge_request.source_branch) }
let!(:ci_build) { create(:ci_build, pipeline: pipeline) }
before do
@@ -47,7 +47,7 @@ feature 'Merge When Build Succeeds', feature: true, js: true do
merge_user: user, title: "MepMep", merge_when_build_succeeds: true)
end
- let!(:pipeline) { create(:ci_pipeline, project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch) }
+ let!(:pipeline) { create(:ci_pipeline, project: project, sha: merge_request.diff_head_sha, ref: merge_request.source_branch) }
let!(:ci_build) { create(:ci_build, pipeline: pipeline) }
before do
diff --git a/spec/features/merge_requests/only_allow_merge_if_build_succeeds.rb b/spec/features/merge_requests/only_allow_merge_if_build_succeeds.rb
index 65e9185ec24..80e8b8fc642 100644
--- a/spec/features/merge_requests/only_allow_merge_if_build_succeeds.rb
+++ b/spec/features/merge_requests/only_allow_merge_if_build_succeeds.rb
@@ -19,7 +19,7 @@ feature 'Only allow merge requests to be merged if the build succeeds', feature:
end
context 'when project has CI enabled' do
- let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: merge_request.last_commit.id, ref: merge_request.source_branch) }
+ let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: merge_request.diff_head_sha, ref: merge_request.source_branch) }
context 'when merge requests can only be merged if the build succeeds' do
before do
diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb
index 737efcef45d..0b38c413f44 100644
--- a/spec/features/notes_on_merge_requests_spec.rb
+++ b/spec/features/notes_on_merge_requests_spec.rb
@@ -135,6 +135,28 @@ describe 'Comments', feature: true do
end
end
+ describe 'Handles cross-project system notes', js: true, feature: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public) }
+ let(:project2) { create(:project, :private) }
+ let(:issue) { create(:issue, project: project2) }
+ let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'markdown') }
+ let!(:note) { create(:note_on_merge_request, :system, noteable: merge_request, project: project, note: "mentioned in #{issue.to_reference(project)}") }
+
+ it 'shows the system note' do
+ login_as :admin
+ visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+
+ expect(page).to have_css('.system-note')
+ end
+
+ it 'hides redacted system note' do
+ visit namespace_project_merge_request_path(project.namespace, project, merge_request)
+
+ expect(page).not_to have_css('.system-note')
+ end
+ end
+
describe 'On a merge request diff', js: true, feature: true do
let(:merge_request) { create(:merge_request) }
let(:project) { merge_request.source_project }
@@ -166,12 +188,14 @@ describe 'Comments', feature: true do
click_diff_line
is_expected.
- to have_css("tr[id='#{line_code}'] + .js-temp-notes-holder form",
+ to have_css("form[data-line-code='#{line_code}']",
count: 1)
end
it 'should be removed when canceled' do
- page.within(".diff-file form[id$='#{line_code}-true']") do
+ is_expected.to have_css('.js-temp-notes-holder')
+
+ page.within("form[data-line-code='#{line_code}']") do
find('.js-close-discussion-note-form').trigger('click')
end
@@ -229,6 +253,7 @@ describe 'Comments', feature: true do
end
def click_diff_line(data = line_code)
- execute_script("$('button[data-line-code=\"#{data}\"]').click()")
+ find(".line_holder[id='#{data}'] td.line_content").hover
+ find(".line_holder[id='#{data}'] button").trigger('click')
end
end
diff --git a/spec/features/pipelines_spec.rb b/spec/features/pipelines_spec.rb
index 98703ef3ac4..7f861db1969 100644
--- a/spec/features/pipelines_spec.rb
+++ b/spec/features/pipelines_spec.rb
@@ -62,6 +62,20 @@ describe "Pipelines" do
end
end
+ context 'with manual actions' do
+ let!(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'manual build', stage: 'test', commands: 'test') }
+
+ before { visit namespace_project_pipelines_path(project.namespace, project) }
+
+ it { expect(page).to have_link('Manual build') }
+
+ context 'when playing' do
+ before { click_link('Manual build') }
+
+ it { expect(manual.reload).to be_pending }
+ end
+ end
+
context 'for generic statuses' do
context 'when running' do
let!(:running) { create(:generic_commit_status, status: 'running', pipeline: pipeline, stage: 'test') }
@@ -117,13 +131,14 @@ describe "Pipelines" do
@success = create(:ci_build, :success, pipeline: pipeline, stage: 'build', name: 'build')
@failed = create(:ci_build, :failed, pipeline: pipeline, stage: 'test', name: 'test', commands: 'test')
@running = create(:ci_build, :running, pipeline: pipeline, stage: 'deploy', name: 'deploy')
+ @manual = create(:ci_build, :manual, pipeline: pipeline, stage: 'deploy', name: 'manual build')
@external = create(:generic_commit_status, status: 'success', pipeline: pipeline, name: 'jenkins', stage: 'external')
end
before { visit namespace_project_pipeline_path(project.namespace, project, pipeline) }
it 'showing a list of builds' do
- expect(page).to have_content('Tests')
+ expect(page).to have_content('Test')
expect(page).to have_content(@success.id)
expect(page).to have_content('Deploy')
expect(page).to have_content(@failed.id)
@@ -131,6 +146,7 @@ describe "Pipelines" do
expect(page).to have_content(@external.id)
expect(page).to have_content('Retry failed')
expect(page).to have_content('Cancel running')
+ expect(page).to have_link('Play')
end
context 'retrying builds' do
@@ -154,6 +170,12 @@ describe "Pipelines" do
it { expect(page).to have_selector('.ci-canceled') }
end
end
+
+ context 'playing manual build' do
+ before { click_link('Play') }
+
+ it { expect(@manual.reload).to be_pending }
+ end
end
describe 'POST /:project/pipelines' do
diff --git a/spec/features/projects/badges/list_spec.rb b/spec/features/projects/badges/list_spec.rb
index 51be81d634c..01e90618a98 100644
--- a/spec/features/projects/badges/list_spec.rb
+++ b/spec/features/projects/badges/list_spec.rb
@@ -1,8 +1,6 @@
require 'spec_helper'
feature 'list of badges' do
- include Select2Helper
-
background do
user = create(:user)
project = create(:project)
@@ -24,7 +22,11 @@ feature 'list of badges' do
end
scenario 'user changes current ref on badges list page', js: true do
- select2('improve/awesome', from: '#ref')
+ first('.js-project-refs-dropdown').click
+
+ page.within '.project-refs-form' do
+ click_link 'improve/awesome'
+ end
expect(page).to have_content 'badges/improve/awesome/build.svg'
end
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/gitignore_dropdown_spec.rb b/spec/features/projects/files/gitignore_dropdown_spec.rb
index 073a83b6896..9ebef505b92 100644
--- a/spec/features/projects/files/gitignore_dropdown_spec.rb
+++ b/spec/features/projects/files/gitignore_dropdown_spec.rb
@@ -24,6 +24,7 @@ feature 'User wants to add a .gitignore file', feature: true do
end
wait_for_ajax
+ expect(page).to have_css('.gitignore-selector .dropdown-toggle-text', text: 'Rails')
expect(page).to have_content('/.bundle')
expect(page).to have_content('# Gemfile.lock, .ruby-version, .ruby-gemset')
end
diff --git a/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb
new file mode 100644
index 00000000000..fca40f68b01
--- /dev/null
+++ b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb
@@ -0,0 +1,31 @@
+require 'spec_helper'
+
+feature 'User wants to add a .gitlab-ci.yml file', feature: true do
+ include WaitForAjax
+
+ before do
+ user = create(:user)
+ project = create(:project)
+ project.team << [user, :master]
+ login_as user
+ visit namespace_project_new_blob_path(project.namespace, project, 'master', file_name: '.gitlab-ci.yml')
+ end
+
+ scenario 'user can see .gitlab-ci.yml dropdown' do
+ expect(page).to have_css('.gitlab-ci-yml-selector')
+ end
+
+ scenario 'user can pick a template from the dropdown', js: 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
+ end
+ wait_for_ajax
+
+ expect(page).to have_css('.gitlab-ci-yml-selector .dropdown-toggle-text', text: 'Jekyll')
+ expect(page).to have_content('This file is a template, and might need editing before it works on your project')
+ expect(page).to have_content('jekyll build -d test')
+ end
+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
index 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
Binary files differ
diff --git a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
index 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/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb
index 6a39c302f55..98ba93b4036 100644
--- a/spec/features/projects/labels/update_prioritization_spec.rb
+++ b/spec/features/projects/labels/update_prioritization_spec.rb
@@ -76,7 +76,7 @@ feature 'Prioritize labels', feature: true do
expect(page.all('li').last).to have_content('bug')
end
- visit current_url
+ refresh
wait_for_ajax
page.within('.prioritized-labels') do
diff --git a/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb b/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb
index 4d5d656f00c..ff9b6007806 100644
--- a/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb
+++ b/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb
@@ -5,9 +5,6 @@ feature 'Projects > Members > Group member cannot request access to his group pr
let(:group) { create(:group) }
let(:project) { create(:project, namespace: group) }
- background do
- end
-
scenario 'owner does not see the request access button' do
group.add_owner(user)
login_and_visit_project_page(user)
diff --git a/spec/features/projects/members/master_manages_access_requests_spec.rb b/spec/features/projects/members/master_manages_access_requests_spec.rb
index 5fe4caa12f0..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,8 +40,8 @@ feature 'Projects > Members > Master manages access requests', feature: true do
end
def expect_visible_access_request(project, user)
- expect(project.members.request.exists?(user_id: user)).to be_truthy
- expect(page).to have_content "#{project.name} access requests (1)"
+ expect(project.requesters.exists?(user_id: user)).to be_truthy
+ expect(page).to have_content "#{project.name} access requests 1"
expect(page).to have_content user.name
end
end
diff --git a/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb b/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb
new file mode 100644
index 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/member_leaves_project_spec.rb b/spec/features/projects/members/member_leaves_project_spec.rb
new file mode 100644
index 00000000000..79dec442818
--- /dev/null
+++ b/spec/features/projects/members/member_leaves_project_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+feature 'Projects > Members > Member leaves project', feature: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+
+ background do
+ project.team << [user, :developer]
+ login_as(user)
+ visit namespace_project_path(project.namespace, project)
+ end
+
+ scenario 'user leaves project' do
+ click_link 'Leave Project'
+
+ expect(current_path).to eq(dashboard_projects_path)
+ expect(project.users.exists?(user.id)).to be_falsey
+ end
+end
diff --git a/spec/features/projects/members/owner_cannot_leave_project_spec.rb b/spec/features/projects/members/owner_cannot_leave_project_spec.rb
new file mode 100644
index 00000000000..67811b1048e
--- /dev/null
+++ b/spec/features/projects/members/owner_cannot_leave_project_spec.rb
@@ -0,0 +1,16 @@
+require 'spec_helper'
+
+feature 'Projects > Members > Owner cannot leave project', feature: true do
+ let(:owner) { create(:user) }
+ let(:project) { create(:project) }
+
+ background do
+ project.team << [owner, :owner]
+ login_as(owner)
+ visit namespace_project_path(project.namespace, project)
+ end
+
+ scenario 'user does not see a "Leave Project" link' do
+ expect(page).not_to have_content 'Leave Project'
+ end
+end
diff --git a/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb b/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb
new file mode 100644
index 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 fd92a3a2f0c..f2fe3ef364d 100644
--- a/spec/features/projects/members/user_requests_access_spec.rb
+++ b/spec/features/projects/members/user_requests_access_spec.rb
@@ -17,16 +17,17 @@ 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'
+ expect(page).not_to have_content 'Leave Project'
end
scenario 'user is not listed in the project members page' do
click_link 'Request Access'
- expect(project.members.request.exists?(user_id: user)).to be_truthy
+ expect(project.requesters.exists?(user_id: user)).to be_truthy
open_project_settings_menu
click_link 'Members'
@@ -40,11 +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/projects_spec.rb b/spec/features/projects_spec.rb
index 9dd0378d165..6fa8298d489 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -70,22 +70,6 @@ feature 'Project', feature: true do
end
end
- describe 'leave project link' do
- let(:user) { create(:user) }
- let(:project) { create(:project, namespace: user.namespace) }
-
- before do
- login_with(user)
- project.team.add_user(user, Gitlab::Access::MASTER)
- visit namespace_project_path(project.namespace, project)
- end
-
- it 'click project-settings and find leave project' do
- find('#project-settings-button').click
- expect(page).to have_link('Leave Project')
- end
- end
-
describe 'project title' do
include WaitForAjax
diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb
new file mode 100644
index 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/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb
index 8625ea6bc10..b6acc509342 100644
--- a/spec/features/security/project/internal_access_spec.rb
+++ b/spec/features/security/project/internal_access_spec.rb
@@ -288,4 +288,161 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_denied_for :external }
it { is_expected.to be_denied_for :visitor }
end
+
+ describe "GET /:project_path/pipelines" do
+ subject { namespace_project_pipelines_path(project.namespace, project) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/pipelines/:id" do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ subject { namespace_project_pipeline_path(project.namespace, project, pipeline) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/builds" do
+ subject { namespace_project_builds_path(project.namespace, project) }
+
+ context "when allowed for public and internal" do
+ before { project.update(public_builds: true) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ context "when disallowed for public and internal" do
+ before { project.update(public_builds: false) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+ end
+
+ describe "GET /:project_path/builds/:id" do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:build) { create(:ci_build, pipeline: pipeline) }
+ subject { namespace_project_build_path(project.namespace, project, build.id) }
+
+ context "when allowed for public and internal" do
+ before { project.update(public_builds: true) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ context "when disallowed for public and internal" do
+ before { project.update(public_builds: false) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+ end
+
+ describe "GET /:project_path/environments" do
+ subject { namespace_project_environments_path(project.namespace, project) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/environments/:id" do
+ let(:environment) { create(:environment, project: project) }
+ subject { namespace_project_environment_path(project.namespace, project, environment) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/environments/new" do
+ subject { new_namespace_project_environment_path(project.namespace, project) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_denied_for reporter }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/container_registry" do
+ before do
+ stub_container_registry_tags('latest')
+ stub_container_registry_config(enabled: true)
+ end
+
+ subject { namespace_project_container_registry_index_path(project.namespace, project) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
end
diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb
index 544270b4037..ccb5c06dab0 100644
--- a/spec/features/security/project/private_access_spec.rb
+++ b/spec/features/security/project/private_access_spec.rb
@@ -260,4 +260,125 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_denied_for :external }
it { is_expected.to be_denied_for :visitor }
end
+
+ describe "GET /:project_path/pipelines" do
+ subject { namespace_project_pipelines_path(project.namespace, project) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/pipelines/:id" do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ subject { namespace_project_pipeline_path(project.namespace, project, pipeline) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/builds" do
+ subject { namespace_project_builds_path(project.namespace, project) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/builds/:id" do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:build) { create(:ci_build, pipeline: pipeline) }
+ subject { namespace_project_build_path(project.namespace, project, build.id) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/environments" do
+ subject { namespace_project_environments_path(project.namespace, project) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/environments/:id" do
+ let(:environment) { create(:environment, project: project) }
+ subject { namespace_project_environment_path(project.namespace, project, environment) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/environments/new" do
+ subject { new_namespace_project_environment_path(project.namespace, project) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_denied_for reporter }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
+
+ describe "GET /:project_path/container_registry" do
+ before do
+ stub_container_registry_tags('latest')
+ stub_container_registry_config(enabled: true)
+ end
+
+ subject { namespace_project_container_registry_index_path(project.namespace, project) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_denied_for guest }
+ it { is_expected.to be_denied_for :user }
+ it { is_expected.to be_denied_for :external }
+ it { is_expected.to be_denied_for :visitor }
+ end
end
diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb
index f6c6687e162..985663e7c98 100644
--- a/spec/features/security/project/public_access_spec.rb
+++ b/spec/features/security/project/public_access_spec.rb
@@ -109,6 +109,35 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :external }
end
+ describe "GET /:project_path/pipelines" do
+ subject { namespace_project_pipelines_path(project.namespace, project) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :external }
+ it { is_expected.to be_allowed_for :visitor }
+ end
+
+ describe "GET /:project_path/pipelines/:id" do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ subject { namespace_project_pipeline_path(project.namespace, project, pipeline) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :external }
+ it { is_expected.to be_allowed_for :visitor }
+ end
+
describe "GET /:project_path/builds" do
subject { namespace_project_builds_path(project.namespace, project) }
@@ -191,7 +220,7 @@ describe "Public Project Access", feature: true do
describe "GET /:project_path/environments/:id" do
let(:environment) { create(:environment, project: project) }
- subject { namespace_project_environments_path(project.namespace, project, environment) }
+ subject { namespace_project_environment_path(project.namespace, project, environment) }
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for owner }
@@ -397,4 +426,23 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_denied_for :external }
it { is_expected.to be_denied_for :visitor }
end
+
+ describe "GET /:project_path/container_registry" do
+ before do
+ stub_container_registry_tags('latest')
+ stub_container_registry_config(enabled: true)
+ end
+
+ subject { namespace_project_container_registry_index_path(project.namespace, project) }
+
+ it { is_expected.to be_allowed_for :admin }
+ it { is_expected.to be_allowed_for owner }
+ it { is_expected.to be_allowed_for master }
+ it { is_expected.to be_allowed_for developer }
+ it { is_expected.to be_allowed_for reporter }
+ it { is_expected.to be_allowed_for guest }
+ it { is_expected.to be_allowed_for :user }
+ it { is_expected.to be_allowed_for :external }
+ it { is_expected.to be_allowed_for :visitor }
+ end
end
diff --git a/spec/features/signup_spec.rb b/spec/features/signup_spec.rb
index 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/u2f_spec.rb b/spec/features/u2f_spec.rb
index 14613754f74..9335f5bf120 100644
--- a/spec/features/u2f_spec.rb
+++ b/spec/features/u2f_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: true, js: true do
+ before { allow_any_instance_of(U2fHelper).to receive(:inject_u2f_api?).and_return(true) }
+
def register_u2f_device(u2f_device = nil)
u2f_device ||= FakeU2fDevice.new(page)
u2f_device.respond_to_u2f_registration
@@ -208,21 +210,52 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature:
expect(page.body).to match('Authentication via U2F device failed')
end
end
- end
- describe "when two-factor authentication is disabled" do
- let(:user) { create(:user) }
+ describe "when more than one device has been registered by the same user" do
+ it "allows logging in with either device" do
+ # Register first device
+ user = login_as(:user)
+ user.update_attribute(:otp_required_for_login, true)
+ visit profile_two_factor_auth_path
+ expect(page).to have_content("Your U2F device needs to be set up.")
+ first_device = register_u2f_device
+
+ # Register second device
+ visit profile_two_factor_auth_path
+ expect(page).to have_content("Your U2F device needs to be set up.")
+ second_device = register_u2f_device
+ logout
+
+ # Authenticate as both devices
+ [first_device, second_device].each do |device|
+ login_as(user)
+ device.respond_to_u2f_authentication
+ click_on "Login Via U2F Device"
+ expect(page.body).to match('We heard back from your U2F device')
+ click_on "Authenticate via U2F Device"
- before do
- login_as(user)
- user.update_attribute(:otp_required_for_login, true)
- visit profile_account_path
- click_on 'Manage Two-Factor Authentication'
- register_u2f_device
+ expect(page.body).to match('Signed in successfully')
+
+ logout
+ end
+ end
end
- it "deletes u2f registrations" do
- expect { click_on "Disable" }.to change { U2fRegistration.count }.from(1).to(0)
+ describe "when two-factor authentication is disabled" do
+ let(:user) { create(:user) }
+
+ before do
+ user = login_as(:user)
+ user.update_attribute(:otp_required_for_login, true)
+ visit profile_account_path
+ click_on 'Manage Two-Factor Authentication'
+ expect(page).to have_content("Your U2F device needs to be set up.")
+ register_u2f_device
+ end
+
+ it "deletes u2f registrations" do
+ expect { click_on "Disable" }.to change { U2fRegistration.count }.by(-1)
+ end
end
end
end
diff --git a/spec/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/notes_finder_spec.rb b/spec/finders/notes_finder_spec.rb
index 1bd354815e4..8db897b1646 100644
--- a/spec/finders/notes_finder_spec.rb
+++ b/spec/finders/notes_finder_spec.rb
@@ -11,7 +11,7 @@ describe NotesFinder do
project.team << [user, :master]
end
- describe :execute do
+ describe '#execute' do
let(:params) { { target_id: commit.id, target_type: 'commit', last_fetched_at: 1.hour.ago.to_i } }
before do
diff --git a/spec/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/blockquote_fence_after.md b/spec/fixtures/blockquote_fence_after.md
new file mode 100644
index 00000000000..2652a842c0e
--- /dev/null
+++ b/spec/fixtures/blockquote_fence_after.md
@@ -0,0 +1,115 @@
+Single `>>>` inside code block:
+
+```
+# Code
+>>>
+# Code
+```
+
+Double `>>>` inside code block:
+
+```txt
+# Code
+>>>
+# Code
+>>>
+# Code
+```
+
+Blockquote outside code block:
+
+> Quote
+
+Code block inside blockquote:
+
+> Quote
+>
+> ```
+> # Code
+> ```
+>
+> Quote
+
+Single `>>>` inside code block inside blockquote:
+
+> Quote
+>
+> ```
+> # Code
+> >>>
+> # Code
+> ```
+>
+> Quote
+
+Double `>>>` inside code block inside blockquote:
+
+> Quote
+>
+> ```
+> # Code
+> >>>
+> # Code
+> >>>
+> # Code
+> ```
+>
+> Quote
+
+Single `>>>` inside HTML:
+
+<pre>
+# Code
+>>>
+# Code
+</pre>
+
+Double `>>>` inside HTML:
+
+<pre>
+# Code
+>>>
+# Code
+>>>
+# Code
+</pre>
+
+Blockquote outside HTML:
+
+> Quote
+
+HTML inside blockquote:
+
+> Quote
+>
+> <pre>
+> # Code
+> </pre>
+>
+> Quote
+
+Single `>>>` inside HTML inside blockquote:
+
+> Quote
+>
+> <pre>
+> # Code
+> >>>
+> # Code
+> </pre>
+>
+> Quote
+
+Double `>>>` inside HTML inside blockquote:
+
+> Quote
+>
+> <pre>
+> # Code
+> >>>
+> # Code
+> >>>
+> # Code
+> </pre>
+>
+> Quote
diff --git a/spec/fixtures/blockquote_fence_before.md b/spec/fixtures/blockquote_fence_before.md
new file mode 100644
index 00000000000..d52eec72896
--- /dev/null
+++ b/spec/fixtures/blockquote_fence_before.md
@@ -0,0 +1,131 @@
+Single `>>>` inside code block:
+
+```
+# Code
+>>>
+# Code
+```
+
+Double `>>>` inside code block:
+
+```txt
+# Code
+>>>
+# Code
+>>>
+# Code
+```
+
+Blockquote outside code block:
+
+>>>
+Quote
+>>>
+
+Code block inside blockquote:
+
+>>>
+Quote
+
+```
+# Code
+```
+
+Quote
+>>>
+
+Single `>>>` inside code block inside blockquote:
+
+>>>
+Quote
+
+```
+# Code
+>>>
+# Code
+```
+
+Quote
+>>>
+
+Double `>>>` inside code block inside blockquote:
+
+>>>
+Quote
+
+```
+# Code
+>>>
+# Code
+>>>
+# Code
+```
+
+Quote
+>>>
+
+Single `>>>` inside HTML:
+
+<pre>
+# Code
+>>>
+# Code
+</pre>
+
+Double `>>>` inside HTML:
+
+<pre>
+# Code
+>>>
+# Code
+>>>
+# Code
+</pre>
+
+Blockquote outside HTML:
+
+>>>
+Quote
+>>>
+
+HTML inside blockquote:
+
+>>>
+Quote
+
+<pre>
+# Code
+</pre>
+
+Quote
+>>>
+
+Single `>>>` inside HTML inside blockquote:
+
+>>>
+Quote
+
+<pre>
+# Code
+>>>
+# Code
+</pre>
+
+Quote
+>>>
+
+Double `>>>` inside HTML inside blockquote:
+
+>>>
+Quote
+
+<pre>
+# Code
+>>>
+# Code
+>>>
+# Code
+</pre>
+
+Quote
+>>>
diff --git a/spec/fixtures/container_registry/tag_manifest.json b/spec/fixtures/container_registry/tag_manifest.json
index 1b6008e2872..8d1b874c29b 100644
--- a/spec/fixtures/container_registry/tag_manifest.json
+++ b/spec/fixtures/container_registry/tag_manifest.json
@@ -1 +1,16 @@
-{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/octet-stream","size":1145,"digest":"sha256:d7a513a663c1a6dcdba9ed832ca53c02ac2af0c333322cd6ca92936d1d9917ac"},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","size":2319870,"digest":"sha256:420890c9e918b6668faaedd9000e220190f2493b0693ee563ebd7b4cc754a57d"}]}
+{
+ "schemaVersion": 2,
+ "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
+ "config": {
+ "mediaType": "application/octet-stream",
+ "size": 1145,
+ "digest": "sha256:d7a513a663c1a6dcdba9ed832ca53c02ac2af0c333322cd6ca92936d1d9917ac"
+ },
+ "layers": [
+ {
+ "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
+ "size": 2319870,
+ "digest": "sha256:420890c9e918b6668faaedd9000e220190f2493b0693ee563ebd7b4cc754a57d"
+ }
+ ]
+}
diff --git a/spec/fixtures/dk.png b/spec/fixtures/dk.png
index 87ce25e877a..1247f2fecd7 100644
--- a/spec/fixtures/dk.png
+++ b/spec/fixtures/dk.png
Binary files differ
diff --git a/spec/fixtures/parallel_diff_result.yml b/spec/fixtures/parallel_diff_result.yml
index a8b7907d4ba..37066c8e930 100644
--- a/spec/fixtures/parallel_diff_result.yml
+++ b/spec/fixtures/parallel_diff_result.yml
@@ -3,322 +3,798 @@
:type: match
:number: 6
:text: "@@ -6,12 +6,18 @@ module Popen"
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6
+ :line_code:
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line:
+ :new_line:
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type: match
:number: 6
:text: "@@ -6,12 +6,18 @@ module Popen"
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6
+ :line_code:
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line:
+ :new_line:
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type:
:number: 6
:text: |2
<span id="LC6" class="line"></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 6
+ :new_line: 6
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type:
:number: 6
:text: |2
<span id="LC6" class="line"></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 6
+ :new_line: 6
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type:
:number: 7
:text: |2
<span id="LC7" class="line"> <span class="k">def</span> <span class="nf">popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 7
+ :new_line: 7
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type:
:number: 7
:text: |2
<span id="LC7" class="line"> <span class="k">def</span> <span class="nf">popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">path</span><span class="o">=</span><span class="kp">nil</span><span class="p">)</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 7
+ :new_line: 7
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type:
:number: 8
:text: |2
<span id="LC8" class="line"> <span class="k">unless</span> <span class="n">cmd</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Array</span><span class="p">)</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 8
+ :new_line: 8
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type:
:number: 8
:text: |2
<span id="LC8" class="line"> <span class="k">unless</span> <span class="n">cmd</span><span class="p">.</span><span class="nf">is_a?</span><span class="p">(</span><span class="no">Array</span><span class="p">)</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 8
+ :new_line: 8
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type: old
:number: 9
:text: |
- -<span id="LC9" class="line"> <span class="k">raise</span> <span class="s2">&quot;System commands must be given as an array of strings&quot;</span></span>
+ -<span id="LC9" class="line"> <span class="k">raise</span> <span class="s2">"System commands must be given as an array of strings"</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 9
+ :new_line:
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type: new
:number: 9
:text: |
- +<span id="LC9" class="line"> <span class="k">raise</span> <span class="no"><span class='idiff left'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff right'> </span><span class="s2">&quot;System commands must be given as an array of strings&quot;</span></span>
+ +<span id="LC9" class="line"> <span class="k">raise</span> <span class="no"><span class='idiff left'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff right'> </span><span class="s2">"System commands must be given as an array of strings"</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line:
+ :new_line: 9
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type:
:number: 10
:text: |2
<span id="LC10" class="line"> <span class="k">end</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 10
+ :new_line: 10
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type:
:number: 10
:text: |2
<span id="LC10" class="line"> <span class="k">end</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 10
+ :new_line: 10
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type:
:number: 11
:text: |2
<span id="LC11" class="line"></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_11_11
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 11
+ :new_line: 11
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type:
:number: 11
:text: |2
<span id="LC11" class="line"></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_11_11
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 11
+ :new_line: 11
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type:
:number: 12
:text: |2
<span id="LC12" class="line"> <span class="n">path</span> <span class="o">||=</span> <span class="no">Dir</span><span class="p">.</span><span class="nf">pwd</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_12_12
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 12
+ :new_line: 12
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type:
:number: 12
:text: |2
<span id="LC12" class="line"> <span class="n">path</span> <span class="o">||=</span> <span class="no">Dir</span><span class="p">.</span><span class="nf">pwd</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_12_12
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 12
+ :new_line: 12
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type: old
:number: 13
:text: |
- -<span id="LC13" class="line"> <span class="n">vars</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">&quot;PWD&quot;</span> <span class="o">=&gt;</span> <span class="n">path</span> <span class="p">}</span></span>
+ -<span id="LC13" class="line"> <span class="n">vars</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">"PWD"</span> <span class="o">=&gt;</span> <span class="n">path</span> <span class="p">}</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_13_13
- :right:
- :type: old
- :number:
- :text: ''
- :line_code:
-- :left:
- :type: old
- :number: 14
- :text: |
- -<span id="LC14" class="line"> <span class="n">options</span> <span class="o">=</span> <span class="p">{</span> <span class="ss">chdir: </span><span class="n">path</span> <span class="p">}</span></span>
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_13
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 13
+ :new_line:
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type: new
:number: 13
:text: |
+<span id="LC13" class="line"></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_13
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line:
+ :new_line: 13
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
- :type:
- :number:
- :text: ''
- :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_14
+ :type: old
+ :number: 14
+ :text: |
+ -<span id="LC14" class="line"> <span class="n">options</span> <span class="o">=</span> <span class="p">{</span> <span class="ss">chdir: </span><span class="n">path</span> <span class="p">}</span></span>
+ :line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_13
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 14
+ :new_line:
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type: new
:number: 14
:text: |
+<span id="LC14" class="line"> <span class="n">vars</span> <span class="o">=</span> <span class="p">{</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_14
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line:
+ :new_line: 14
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type:
:number:
:text: ''
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line:
+ :new_line: 15
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type: new
:number: 15
:text: |
- +<span id="LC15" class="line"> <span class="s2">&quot;PWD&quot;</span> <span class="o">=&gt;</span> <span class="n">path</span></span>
+ +<span id="LC15" class="line"> <span class="s2">"PWD"</span> <span class="o">=&gt;</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">&quot;&quot;</span></span>
+ <span id="LC26" class="line"> <span class="vi">@cmd_output</span> <span class="o">=</span> <span class="s2">""</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 20
+ :new_line: 26
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type:
:number: 26
:text: |2
- <span id="LC26" class="line"> <span class="vi">@cmd_output</span> <span class="o">=</span> <span class="s2">&quot;&quot;</span></span>
+ <span id="LC26" class="line"> <span class="vi">@cmd_output</span> <span class="o">=</span> <span class="s2">""</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 20
+ :new_line: 26
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type:
:number: 21
:text: |2
<span id="LC27" class="line"> <span class="vi">@cmd_status</span> <span class="o">=</span> <span class="mi">0</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_21_27
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 21
+ :new_line: 27
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type:
:number: 27
:text: |2
<span id="LC27" class="line"> <span class="vi">@cmd_status</span> <span class="o">=</span> <span class="mi">0</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_21_27
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 21
+ :new_line: 27
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type:
:number:
:text: ''
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_28
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line:
+ :new_line: 28
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type: new
:number: 28
:text: |
+<span id="LC28" class="line"></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_28
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line:
+ :new_line: 28
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type:
:number: 22
:text: |2
<span id="LC29" class="line"> <span class="no">Open3</span><span class="p">.</span><span class="nf">popen3</span><span class="p">(</span><span class="n">vars</span><span class="p">,</span> <span class="o">*</span><span class="n">cmd</span><span class="p">,</span> <span class="n">options</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">stdin</span><span class="p">,</span> <span class="n">stdout</span><span class="p">,</span> <span class="n">stderr</span><span class="p">,</span> <span class="n">wait_thr</span><span class="o">|</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_29
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 22
+ :new_line: 29
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
:right:
:type:
:number: 29
:text: |2
<span id="LC29" class="line"> <span class="no">Open3</span><span class="p">.</span><span class="nf">popen3</span><span class="p">(</span><span class="n">vars</span><span class="p">,</span> <span class="o">*</span><span class="n">cmd</span><span class="p">,</span> <span class="n">options</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">stdin</span><span class="p">,</span> <span class="n">stdout</span><span class="p">,</span> <span class="n">stderr</span><span class="p">,</span> <span class="n">wait_thr</span><span class="o">|</span></span>
:line_code: 2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_29
+ :position: !ruby/object:Gitlab::Diff::Position
+ attributes:
+ :old_path: files/ruby/popen.rb
+ :new_path: files/ruby/popen.rb
+ :old_line: 22
+ :new_line: 29
+ :base_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :start_sha: 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9
+ :head_sha: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
- :left:
:type:
:number: 23
:text: |2
<span id="LC30" class="line"> <span class="vi">@cmd_output</span> <span class="o">&lt;&lt;</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">&lt;&lt;</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">&lt;&lt;</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">&lt;&lt;</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/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index f6c1005d265..bb28866f010 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -174,51 +174,6 @@ describe ApplicationHelper do
end
end
- describe 'grouped_options_refs' do
- let(:options) { helper.grouped_options_refs }
- let(:project) { create(:project) }
-
- before do
- assign(:project, project)
-
- # Override Rails' grouped_options_for_select helper to just return the
- # first argument (`options`), since it's easier to work with than the
- # generated HTML.
- allow(helper).to receive(:grouped_options_for_select).
- and_wrap_original { |_, *args| args.first }
- end
-
- it 'includes a list of branch names' do
- expect(options[0][0]).to eq('Branches')
- expect(options[0][1]).to include('master', 'feature')
- end
-
- it 'includes a list of tag names' do
- expect(options[1][0]).to eq('Tags')
- expect(options[1][1]).to include('v1.0.0', 'v1.1.0')
- end
-
- it 'includes a specific commit ref if defined' do
- # Must be an instance variable
- ref = '2ed06dc41dbb5936af845b87d79e05bbf24c73b8'
- assign(:ref, ref)
-
- expect(options[2][0]).to eq('Commit')
- expect(options[2][1]).to eq([ref])
- end
-
- it 'sorts tags in a natural order' do
- # Stub repository.tag_names to make sure we get some valid testing data
- expect(project.repository).to receive(:tag_names).
- and_return(['v1.0.9', 'v1.0.10', 'v2.0', 'v3.1.4.2', 'v2.0rc1¿',
- 'v1.0.9a', 'v2.0-rc1', 'v2.0rc2'])
-
- expect(options[1][1]).
- to eq(['v3.1.4.2', 'v2.0', 'v2.0rc2', 'v2.0rc1¿', 'v2.0-rc1', 'v1.0.10',
- 'v1.0.9', 'v1.0.9a'])
- end
- end
-
describe 'simple_sanitize' do
let(:a_tag) { '<a href="#">Foo</a>' }
diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb
index 6d1c02db297..bd0108f9938 100644
--- a/spec/helpers/blob_helper_spec.rb
+++ b/spec/helpers/blob_helper_spec.rb
@@ -16,19 +16,19 @@ describe BlobHelper do
describe '#highlight' do
it 'should return plaintext for unknown lexer context' do
- result = helper.highlight(blob_name, no_context_content, nowrap: true)
- expect(result).to eq('<span id="LC1" class="line">:type &quot;assem&quot;))</span>')
+ result = helper.highlight(blob_name, no_context_content)
+ expect(result).to eq(%[<pre class="code highlight"><code><span id="LC1" class="line">:type "assem"))</span></code></pre>])
end
it 'should highlight single block' do
- expected = %Q[<span id="LC1" class="line"><span class="p">(</span><span class="nb">make-pathname</span> <span class="ss">:defaults</span> <span class="nv">name</span></span>
-<span id="LC2" class="line"><span class="ss">:type</span> <span class="s">&quot;assem&quot;</span><span class="p">))</span></span>]
+ expected = %Q[<pre class="code highlight"><code><span id="LC1" class="line"><span class="p">(</span><span class="nb">make-pathname</span> <span class="ss">:defaults</span> <span class="nv">name</span></span>
+<span id="LC2" class="line"><span class="ss">:type</span> <span class="s">"assem"</span><span class="p">))</span></span></code></pre>]
- expect(helper.highlight(blob_name, blob_content, nowrap: true)).to eq(expected)
+ expect(helper.highlight(blob_name, blob_content)).to eq(expected)
end
it 'should highlight multi-line comments' do
- result = helper.highlight(blob_name, multiline_content, nowrap: true)
+ result = helper.highlight(blob_name, multiline_content)
html = Nokogiri::HTML(result)
lines = html.search('.s')
expect(lines.count).to eq(3)
@@ -41,33 +41,19 @@ describe BlobHelper do
let(:blob_name) { 'test.diff' }
let(:blob_content) { "+aaa\n+bbb\n- ccc\n ddd\n"}
let(:expected) do
- %q(<span id="LC1" class="line"><span class="gi">+aaa</span></span>
+ %q(<pre class="code highlight"><code><span id="LC1" class="line"><span class="gi">+aaa</span></span>
<span id="LC2" class="line"><span class="gi">+bbb</span></span>
<span id="LC3" class="line"><span class="gd">- ccc</span></span>
-<span id="LC4" class="line"> ddd</span>)
+<span id="LC4" class="line"> ddd</span></code></pre>)
end
it 'should highlight each line properly' do
- result = helper.highlight(blob_name, blob_content, nowrap: true)
+ result = helper.highlight(blob_name, blob_content)
expect(result).to eq(expected)
end
end
end
- describe "#highlighter" do
- it 'should highlight continued blocks' do
- # Both lines have LC1 as ID since formatter doesn't support continue at the moment
- expected = [
- '<span id="LC1" class="line"><span class="p">(</span><span class="nb">make-pathname</span> <span class="ss">:defaults</span> <span class="nv">name</span></span>',
- '<span id="LC1" class="line"><span class="ss">:type</span> <span class="s">&quot;assem&quot;</span><span class="p">))</span></span>'
- ]
-
- highlighter = helper.highlighter(blob_name, blob_content, nowrap: true)
- result = split_content.map{ |content| highlighter.highlight(content) }
- expect(result).to eq(expected)
- end
- end
-
describe "#sanitize_svg" do
let(:input_svg_path) { File.join(Rails.root, 'spec', 'fixtures', 'unsanitized.svg') }
let(:data) { open(input_svg_path).read }
diff --git a/spec/helpers/ci_status_helper_spec.rb b/spec/helpers/ci_status_helper_spec.rb
index 45199d0f09d..637b02d9388 100644
--- a/spec/helpers/ci_status_helper_spec.rb
+++ b/spec/helpers/ci_status_helper_spec.rb
@@ -7,7 +7,13 @@ describe CiStatusHelper do
let(:failed_commit) { double("Ci::Pipeline", status: 'failed') }
describe 'ci_icon_for_status' do
- it { expect(helper.ci_icon_for_status(success_commit.status)).to include('fa-check') }
- it { expect(helper.ci_icon_for_status(failed_commit.status)).to include('fa-close') }
+ it 'renders to correct svg on success' do
+ expect(helper).to receive(:render).with('shared/icons/icon_status_success.svg', anything)
+ helper.ci_icon_for_status(success_commit.status)
+ end
+ it 'renders the correct svg on failure' do
+ expect(helper).to receive(:render).with('shared/icons/icon_status_failed.svg', anything)
+ helper.ci_icon_for_status(failed_commit.status)
+ end
end
end
diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb
index 52764f41e0d..c2fd2c8a533 100644
--- a/spec/helpers/diff_helper_spec.rb
+++ b/spec/helpers/diff_helper_spec.rb
@@ -9,7 +9,7 @@ describe DiffHelper do
let(:diffs) { commit.diffs }
let(:diff) { diffs.first }
let(:diff_refs) { [commit.parent, commit] }
- let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs) }
+ let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: diff_refs, repository: repository) }
describe 'diff_view' do
it 'returns a valid value when cookie is set' do
@@ -30,26 +30,30 @@ describe DiffHelper do
expect(helper.diff_view).to eq 'inline'
end
end
-
- describe 'diff_hard_limit_enabled?' do
- it 'should return true if param is provided' do
+
+ describe 'diff_options' do
+ it 'should return hard limit for a diff if force diff is true' do
allow(controller).to receive(:params) { { force_show_diff: true } }
- expect(diff_hard_limit_enabled?).to be_truthy
+ expect(diff_options).to include(Commit.max_diff_options)
end
- it 'should return false if param is not provided' do
- expect(diff_hard_limit_enabled?).to be_falsey
+ it 'should return hard limit for a diff if expand_all_diffs is true' do
+ allow(controller).to receive(:params) { { expand_all_diffs: true } }
+ expect(diff_options).to include(Commit.max_diff_options)
end
- end
- describe 'diff_options' do
- it 'should return hard limit for a diff if force diff is true' do
- allow(controller).to receive(:params) { { force_show_diff: true } }
- expect(diff_options).to include(Commit.max_diff_options)
+ it 'should return no collapse false' do
+ expect(diff_options).to include(no_collapse: false)
+ end
+
+ it 'should return no collapse true if expand_all_diffs' do
+ allow(controller).to receive(:params) { { expand_all_diffs: true } }
+ expect(diff_options).to include(no_collapse: true)
end
- it 'should return safe limit for a diff if force diff is false' do
- expect(diff_options).not_to include(:max_lines, :max_files)
+ it 'should return no collapse true if action name diff_for_path' do
+ allow(controller).to receive(:action_name) { 'diff_for_path' }
+ expect(diff_options).to include(no_collapse: true)
end
end
@@ -59,7 +63,7 @@ describe DiffHelper do
end
it 'should return js class when bottom lines should be unfolded' do
- expect(unfold_bottom_class(true)).to eq('js-unfold-bottom')
+ expect(unfold_bottom_class(true)).to include('js-unfold-bottom')
end
end
diff --git a/spec/helpers/events_helper_spec.rb b/spec/helpers/events_helper_spec.rb
index c0d2be98e85..6b5e3d93d48 100644
--- a/spec/helpers/events_helper_spec.rb
+++ b/spec/helpers/events_helper_spec.rb
@@ -57,7 +57,7 @@ describe EventsHelper do
expected = '<pre class="code highlight js-syntax-highlight ruby">' \
"<code><span class=\"k\">def</span> <span class=\"nf\">test</span>\n" \
" <span class=\"s1\">\'hello world\'</span>\n" \
- "<span class=\"k\">end</span>" \
+ "<span class=\"k\">end</span>\n" \
'</code></pre>'
expect(helper.event_note(input)).to eq(expected)
end
diff --git a/spec/helpers/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/time_helper_spec.rb b/spec/helpers/time_helper_spec.rb
index 3f62527c5bb..413ead944b9 100644
--- a/spec/helpers/time_helper_spec.rb
+++ b/spec/helpers/time_helper_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe TimeHelper do
- describe "#duration_in_words" do
+ describe "#time_interval_in_words" do
it "returns minutes and seconds" do
intervals_in_words = {
100 => "1 minute 40 seconds",
@@ -11,26 +11,23 @@ describe TimeHelper do
}
intervals_in_words.each do |interval, expectation|
- expect(duration_in_words(Time.now + interval, Time.now)).to eq(expectation)
+ expect(time_interval_in_words(interval)).to eq(expectation)
end
end
-
- it "calculates interval from now if there is no finished_at" do
- expect(duration_in_words(nil, Time.now - 5)).to eq("5 seconds")
- end
end
- describe "#time_interval_in_words" do
+ describe "#duration_in_numbers" do
it "returns minutes and seconds" do
- intervals_in_words = {
- 100 => "1 minute 40 seconds",
- 121 => "2 minutes 1 second",
- 3721 => "62 minutes 1 second",
- 0 => "0 seconds"
+ duration_in_numbers = {
+ [100, 0] => "01:40",
+ [121, 0] => "02:01",
+ [3721, 0] => "01:02:01",
+ [0, 0] => "00:00",
+ [nil, Time.now.to_i - 42] => "00:42"
}
- intervals_in_words.each do |interval, expectation|
- expect(time_interval_in_words(interval)).to eq(expectation)
+ duration_in_numbers.each do |interval, expectation|
+ expect(duration_in_numbers(*interval)).to eq(expectation)
end
end
end
diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb
index ff98249570d..db3ad1b99e9 100644
--- a/spec/helpers/visibility_level_helper_spec.rb
+++ b/spec/helpers/visibility_level_helper_spec.rb
@@ -1,12 +1,6 @@
require 'spec_helper'
describe VisibilityLevelHelper do
- include Haml::Helpers
-
- before :all do
- init_haml_helpers
- end
-
let(:project) { build(:project) }
let(:group) { build(:group) }
let(:personal_snippet) { build(:personal_snippet) }
@@ -95,6 +89,5 @@ describe VisibilityLevelHelper do
expect(skip_level?(snippet, Gitlab::VisibilityLevel::PRIVATE)).to be_falsey
end
end
-
end
end
diff --git a/spec/initializers/6_validations_spec.rb b/spec/initializers/6_validations_spec.rb
new file mode 100644
index 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 e58f2c80e95..47b4e431823 100644
--- a/spec/initializers/settings_spec.rb
+++ b/spec/initializers/settings_spec.rb
@@ -1,7 +1,7 @@
+require 'spec_helper'
require_relative '../../config/initializers/1_settings'
describe Settings, lib: true do
-
describe '#host_without_www' do
context 'URL with protocol' do
it 'returns the host' do
@@ -40,5 +40,4 @@ describe Settings, lib: true do
end
end
end
-
end
diff --git a/spec/initializers/trusted_proxies_spec.rb b/spec/initializers/trusted_proxies_spec.rb
index 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/application_spec.js.coffee b/spec/javascripts/application_spec.js.coffee
index 8af39c41f2f..4b6a2bb5440 100644
--- a/spec/javascripts/application_spec.js.coffee
+++ b/spec/javascripts/application_spec.js.coffee
@@ -1,4 +1,4 @@
-#= require lib/common_utils
+#= require lib/utils/common_utils
describe 'Application', ->
describe 'disable buttons', ->
diff --git a/spec/javascripts/awards_handler_spec.js.coffee b/spec/javascripts/awards_handler_spec.js.coffee
index 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/javascripts/fixtures/issues_show.html.haml b/spec/javascripts/fixtures/issues_show.html.haml
index 470cabeafbb..06c2ab1e823 100644
--- a/spec/javascripts/fixtures/issues_show.html.haml
+++ b/spec/javascripts/fixtures/issues_show.html.haml
@@ -1,7 +1,7 @@
:css
.hidden { display: none !important; }
-.flash-container
+.flash-container.flash-container-page
.flash-alert
.flash-notice
diff --git a/spec/javascripts/issue_spec.js.coffee b/spec/javascripts/issue_spec.js.coffee
index ea27f36e9b5..d84d80f266b 100644
--- a/spec/javascripts/issue_spec.js.coffee
+++ b/spec/javascripts/issue_spec.js.coffee
@@ -1,3 +1,4 @@
+#= require lib/utils/text_utility
#= require issue
describe 'Issue', ->
@@ -38,7 +39,7 @@ describe 'reopen/close issue', ->
expect(typeof $btnClose.prop('disabled')).toBe('undefined')
$btnClose.trigger('click')
-
+
expect($btnReopen).toBeVisible()
expect($btnClose).toBeHidden()
expect($('div.status-box-closed')).toBeVisible()
@@ -50,7 +51,7 @@ describe 'reopen/close issue', ->
expect(req.type).toBe('PUT')
expect(req.url).toBe('http://goesnowhere.nothing/whereami')
req.success saved: false
-
+
$btnClose = $('a.btn-close')
$btnReopen = $('a.btn-reopen')
$btnClose.attr('href','http://goesnowhere.nothing/whereami')
@@ -59,7 +60,7 @@ describe 'reopen/close issue', ->
expect(typeof $btnClose.prop('disabled')).toBe('undefined')
$btnClose.trigger('click')
-
+
expect($btnReopen).toBeHidden()
expect($btnClose).toBeVisible()
expect($('div.status-box-closed')).toBeHidden()
@@ -73,7 +74,7 @@ describe 'reopen/close issue', ->
expect(req.type).toBe('PUT')
expect(req.url).toBe('http://goesnowhere.nothing/whereami')
req.error()
-
+
$btnClose = $('a.btn-close')
$btnReopen = $('a.btn-reopen')
$btnClose.attr('href','http://goesnowhere.nothing/whereami')
@@ -82,7 +83,7 @@ describe 'reopen/close issue', ->
expect(typeof $btnClose.prop('disabled')).toBe('undefined')
$btnClose.trigger('click')
-
+
expect($btnReopen).toBeHidden()
expect($btnClose).toBeVisible()
expect($('div.status-box-closed')).toBeHidden()
@@ -105,4 +106,4 @@ describe 'reopen/close issue', ->
expect($btnReopen).toBeHidden()
expect($btnClose).toBeVisible()
expect($('div.status-box-open')).toBeVisible()
- expect($('div.status-box-closed')).toBeHidden() \ No newline at end of file
+ expect($('div.status-box-closed')).toBeHidden()
diff --git a/spec/javascripts/project_title_spec.js.coffee b/spec/javascripts/project_title_spec.js.coffee
index 9be29097f4c..0244119fa0e 100644
--- a/spec/javascripts/project_title_spec.js.coffee
+++ b/spec/javascripts/project_title_spec.js.coffee
@@ -1,6 +1,6 @@
#= require bootstrap
#= require select2
-#= require lib/type_utility
+#= require lib/utils/type_utility
#= require gl_dropdown
#= require api
#= require project_select
@@ -22,7 +22,7 @@ describe 'Project Title', ->
@projects_data = fixture.load('projects.json')[0]
spyOn(jQuery, 'ajax').and.callFake (req) =>
- expect(req.url).toBe('/api/v3/projects.json')
+ expect(req.url).toBe('/api/v3/projects.json?simple=true')
d = $.Deferred()
d.resolve @projects_data
d.promise()
diff --git a/spec/javascripts/search_autocomplete_spec.js.coffee b/spec/javascripts/search_autocomplete_spec.js.coffee
index e77177783a7..1c1faca3333 100644
--- a/spec/javascripts/search_autocomplete_spec.js.coffee
+++ b/spec/javascripts/search_autocomplete_spec.js.coffee
@@ -1,8 +1,8 @@
#= require gl_dropdown
#= require search_autocomplete
#= require jquery
-#= require lib/common_utils
-#= require lib/type_utility
+#= require lib/utils/common_utils
+#= require lib/utils/type_utility
#= require fuzzaldrin-plus
diff --git a/spec/javascripts/u2f/authenticate_spec.coffee b/spec/javascripts/u2f/authenticate_spec.coffee
index e8a2892d678..8ffeda11704 100644
--- a/spec/javascripts/u2f/authenticate_spec.coffee
+++ b/spec/javascripts/u2f/authenticate_spec.coffee
@@ -5,13 +5,12 @@
#= require ./mock_u2f_device
describe 'U2FAuthenticate', ->
- U2FUtil.enableTestMode()
fixture.load('u2f/authenticate')
beforeEach ->
@u2fDevice = new MockU2FDevice
@container = $("#js-authenticate-u2f")
- @component = new U2FAuthenticate(@container, {}, "token")
+ @component = new U2FAuthenticate(@container, {sign_requests: []}, "token")
@component.start()
it 'allows authenticating via a U2F device', ->
diff --git a/spec/javascripts/u2f/register_spec.js.coffee b/spec/javascripts/u2f/register_spec.js.coffee
index 0858abeca1a..87dc769792b 100644
--- a/spec/javascripts/u2f/register_spec.js.coffee
+++ b/spec/javascripts/u2f/register_spec.js.coffee
@@ -5,7 +5,6 @@
#= require ./mock_u2f_device
describe 'U2FRegister', ->
- U2FUtil.enableTestMode()
fixture.load('u2f/register')
beforeEach ->
diff --git a/spec/lib/banzai/filter/autolink_filter_spec.rb b/spec/lib/banzai/filter/autolink_filter_spec.rb
index 84c2ddf444e..dca7f997570 100644
--- a/spec/lib/banzai/filter/autolink_filter_spec.rb
+++ b/spec/lib/banzai/filter/autolink_filter_spec.rb
@@ -15,6 +15,16 @@ describe Banzai::Filter::AutolinkFilter, lib: true do
expect(filter(act).to_html).to eq exp
end
+ context 'when the input contains no links' do
+ it 'does not parse_html back the rinku returned value' do
+ act = HTML::Pipeline.parse('<p>This text contains no links to autolink</p>')
+
+ expect_any_instance_of(described_class).not_to receive(:parse_html)
+
+ filter(act).to_html
+ end
+ end
+
context 'Rinku schemes' do
it 'autolinks http' do
doc = filter("See #{link}")
@@ -58,6 +68,16 @@ describe Banzai::Filter::AutolinkFilter, lib: true do
expect(filter(act).to_html).to eq exp
end
end
+
+ context 'when the input contains link' do
+ it 'does parse_html back the rinku returned value' do
+ act = HTML::Pipeline.parse("<p>See #{link}</p>")
+
+ expect_any_instance_of(described_class).to receive(:parse_html).at_least(:once).and_call_original
+
+ filter(act).to_html
+ end
+ end
end
context 'other schemes' do
diff --git a/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb
new file mode 100644
index 00000000000..2799249ae3e
--- /dev/null
+++ b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb
@@ -0,0 +1,14 @@
+require 'rails_helper'
+
+describe Banzai::Filter::BlockquoteFenceFilter, lib: true do
+ include FilterSpecHelper
+
+ it 'converts blockquote fences to blockquote lines' do
+ content = File.read(Rails.root.join('spec/fixtures/blockquote_fence_before.md'))
+ expected = File.read(Rails.root.join('spec/fixtures/blockquote_fence_after.md'))
+
+ output = filter(content)
+
+ expect(output).to eq(expected)
+ end
+end
diff --git a/spec/lib/banzai/filter/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 5b63c946114..a005b4990e7 100644
--- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
@@ -198,4 +198,53 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
expect(doc.to_html).to match(/\(<a.+>Reference<\/a>\.\)/)
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
+ doc = Nokogiri::HTML.fragment('')
+ filter = described_class.new(doc, project: project)
+
+ expect(filter).to receive(:projects_per_reference).
+ and_return({ project.path_with_namespace => project })
+
+ expect(filter).to receive(:references_per_project).
+ and_return({ project.path_with_namespace => Set.new([issue.iid]) })
+
+ expect(filter.issues_per_project).
+ to eq({ project => { issue.iid => issue } })
+ end
+ end
+
+ context 'using an external issue tracker' do
+ it 'returns a Hash containing the issues per project' do
+ doc = Nokogiri::HTML.fragment('')
+ filter = described_class.new(doc, project: project)
+
+ expect(project).to receive(:default_issues_tracker?).and_return(false)
+
+ expect(filter).to receive(:projects_per_reference).
+ and_return({ project.path_with_namespace => project })
+
+ expect(filter).to receive(:references_per_project).
+ and_return({ project.path_with_namespace => Set.new([1]) })
+
+ expect(filter.issues_per_project[project][1]).
+ to be_an_instance_of(ExternalIssue)
+ end
+ end
+ end
end
diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb
index f1064a701d8..9276a154007 100644
--- a/spec/lib/banzai/filter/label_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb
@@ -93,8 +93,8 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
end
it 'links with adjacent text' do
- doc = reference_filter("Label (#{reference}.)")
- expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
+ doc = reference_filter("Label (#{reference}).")
+ expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\)\.))
end
it 'ignores invalid label names' do
@@ -104,6 +104,55 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
end
end
+ context 'String-based single-word references that begin with a digit' do
+ let(:label) { create(:label, name: '2fa', project: project) }
+ let(:reference) { "#{Label.reference_prefix}#{label.name}" }
+
+ it 'links to a valid reference' do
+ doc = reference_filter("See #{reference}")
+
+ expect(doc.css('a').first.attr('href')).to eq urls.
+ namespace_project_issues_url(project.namespace, project, label_name: label.name)
+ expect(doc.text).to eq 'See 2fa'
+ end
+
+ it 'links with adjacent text' do
+ doc = reference_filter("Label (#{reference}).")
+ expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\)\.))
+ end
+
+ it 'ignores invalid label names' do
+ exp = act = "Label #{Label.reference_prefix}#{label.id}#{label.name.reverse}"
+
+ expect(reference_filter(act).to_html).to eq exp
+ end
+ end
+
+ context 'String-based single-word references with special characters' do
+ let(:label) { create(:label, name: '?g.fm&', project: project) }
+ let(:reference) { "#{Label.reference_prefix}#{label.name}" }
+
+ it 'links to a valid reference' do
+ doc = reference_filter("See #{reference}")
+
+ expect(doc.css('a').first.attr('href')).to eq urls.
+ namespace_project_issues_url(project.namespace, project, label_name: label.name)
+ expect(doc.text).to eq 'See ?g.fm&'
+ end
+
+ it 'links with adjacent text' do
+ doc = reference_filter("Label (#{reference}).")
+ expect(doc.to_html).to match(%r(\(<a.+><span.+>\?g\.fm&amp;</span></a>\)\.))
+ end
+
+ it 'ignores invalid label names' do
+ act = "Label #{Label.reference_prefix}#{label.name.reverse}"
+ exp = "Label #{Label.reference_prefix}&amp;mf.g?"
+
+ expect(reference_filter(act).to_html).to eq exp
+ end
+ end
+
context 'String-based multi-word references in quotes' do
let(:label) { create(:label, name: 'gfm references', project: project) }
let(:reference) { label.to_reference(format: :name) }
@@ -128,6 +177,95 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
end
end
+ context 'String-based multi-word references that begin with a digit' do
+ let(:label) { create(:label, name: '2 factor authentication', project: project) }
+ let(:reference) { label.to_reference(format: :name) }
+
+ it 'links to a valid reference' do
+ doc = reference_filter("See #{reference}")
+
+ expect(doc.css('a').first.attr('href')).to eq urls.
+ namespace_project_issues_url(project.namespace, project, label_name: label.name)
+ expect(doc.text).to eq 'See 2 factor authentication'
+ end
+
+ it 'links with adjacent text' do
+ doc = reference_filter("Label (#{reference}.)")
+ expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
+ end
+
+ it 'ignores invalid label names' do
+ exp = act = "Label #{Label.reference_prefix}#{label.id}#{label.name.reverse}"
+
+ expect(reference_filter(act).to_html).to eq exp
+ end
+ end
+
+ context 'String-based multi-word references with special characters in quotes' do
+ let(:label) { create(:label, name: 'g.fm & references?', project: project) }
+ let(:reference) { label.to_reference(format: :name) }
+
+ it 'links to a valid reference' do
+ doc = reference_filter("See #{reference}")
+
+ expect(doc.css('a').first.attr('href')).to eq urls.
+ namespace_project_issues_url(project.namespace, project, label_name: label.name)
+ expect(doc.text).to eq 'See g.fm & references?'
+ end
+
+ it 'links with adjacent text' do
+ doc = reference_filter("Label (#{reference}.)")
+ expect(doc.to_html).to match(%r(\(<a.+><span.+>g\.fm &amp; references\?</span></a>\.\)))
+ end
+
+ it 'ignores invalid label names' do
+ act = %(Label #{Label.reference_prefix}"#{label.name.reverse}")
+ exp = %(Label #{Label.reference_prefix}"?secnerefer &amp; mf.g\")
+
+ expect(reference_filter(act).to_html).to eq exp
+ end
+ end
+
+ describe 'consecutive references' do
+ let(:bug) { create(:label, name: 'bug', project: project) }
+ let(:feature_proposal) { create(:label, name: 'feature proposal', project: project) }
+ let(:technical_debt) { create(:label, name: 'technical debt', project: project) }
+
+ let(:bug_reference) { "#{Label.reference_prefix}#{bug.name}" }
+ let(:feature_proposal_reference) { feature_proposal.to_reference(format: :name) }
+ let(:technical_debt_reference) { technical_debt.to_reference(format: :name) }
+
+ context 'separated with a comma' do
+ let(:references) { "#{bug_reference}, #{feature_proposal_reference}, #{technical_debt_reference}" }
+
+ it 'links to valid references' do
+ doc = reference_filter("See #{references}")
+
+ expect(doc.css('a').map { |a| a.attr('href') }).to match_array([
+ urls.namespace_project_issues_url(project.namespace, project, label_name: bug.name),
+ urls.namespace_project_issues_url(project.namespace, project, label_name: feature_proposal.name),
+ urls.namespace_project_issues_url(project.namespace, project, label_name: technical_debt.name)
+ ])
+ expect(doc.text).to eq 'See bug, feature proposal, technical debt'
+ end
+ end
+
+ context 'separated with a space' do
+ let(:references) { "#{bug_reference} #{feature_proposal_reference} #{technical_debt_reference}" }
+
+ it 'links to valid references' do
+ doc = reference_filter("See #{references}")
+
+ expect(doc.css('a').map { |a| a.attr('href') }).to match_array([
+ urls.namespace_project_issues_url(project.namespace, project, label_name: bug.name),
+ urls.namespace_project_issues_url(project.namespace, project, label_name: feature_proposal.name),
+ urls.namespace_project_issues_url(project.namespace, project, label_name: technical_debt.name)
+ ])
+ expect(doc.text).to eq 'See bug feature proposal technical debt'
+ end
+ end
+ end
+
describe 'edge cases' do
it 'gracefully handles non-references matching the pattern' do
exp = act = '(format nil "~0f" 3.0) ; 3.0'
diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb
index 0e6685f0ffb..b9e4a4eaf0e 100644
--- a/spec/lib/banzai/filter/relative_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb
@@ -132,11 +132,8 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do
path = 'files/images/한글.png'
escaped = Addressable::URI.escape(path)
- # Stub these methods so the file doesn't actually need to be in the repo
- allow_any_instance_of(described_class).
- to receive(:file_exists?).and_return(true)
- allow_any_instance_of(described_class).
- to receive(:image?).with(path).and_return(true)
+ # Stub this method so the file doesn't actually need to be in the repo
+ allow_any_instance_of(described_class).to receive(:uri_type).and_return(:raw)
doc = filter(image(escaped))
expect(doc.at_css('img')['src']).to match '/raw/'
diff --git a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
index 407617f3307..b1370bca833 100644
--- a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
+++ b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
@@ -3,15 +3,35 @@ require 'spec_helper'
describe Banzai::Filter::SyntaxHighlightFilter, lib: true do
include FilterSpecHelper
- it 'highlights valid code blocks' do
- result = filter('<pre><code>def fun end</code>')
- expect(result.to_html).to eq("<pre class=\"code highlight js-syntax-highlight plaintext\"><code>def fun end</code></pre>\n")
+ context "when no language is specified" do
+ it "highlights as plaintext" do
+ result = filter('<pre><code>def fun end</code></pre>')
+ expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext"><code>def fun end</code></pre>')
+ end
end
- it 'passes through invalid code blocks' do
- allow_any_instance_of(described_class).to receive(:block_code).and_raise(StandardError)
+ context "when a valid language is specified" do
+ it "highlights as that language" do
+ result = filter('<pre><code class="ruby">def fun end</code></pre>')
+ expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight ruby"><code><span class="k">def</span> <span class="nf">fun</span> <span class="k">end</span></code></pre>')
+ end
+ end
+
+ context "when an invalid language is specified" do
+ it "highlights as plaintext" do
+ result = filter('<pre><code class="gnuplot">This is a test</code></pre>')
+ expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext"><code>This is a test</code></pre>')
+ end
+ end
+
+ context "when Rouge formatting fails" do
+ before do
+ allow_any_instance_of(Rouge::Formatter).to receive(:format).and_raise(StandardError)
+ end
- result = filter('<pre><code>This is a test</code></pre>')
- expect(result.to_html).to eq('<pre>This is a test</pre>')
+ it "highlights as plaintext" do
+ result = filter('<pre><code class="ruby">This is a test</code></pre>')
+ expect(result.to_html).to eq('<pre class="code highlight"><code>This is a test</code></pre>')
+ end
end
end
diff --git a/spec/lib/banzai/note_renderer_spec.rb b/spec/lib/banzai/note_renderer_spec.rb
new file mode 100644
index 00000000000..98f76f36fd5
--- /dev/null
+++ b/spec/lib/banzai/note_renderer_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe Banzai::NoteRenderer do
+ describe '.render' do
+ it 'renders a Note' do
+ note = double(:note)
+ project = double(:project)
+ wiki = double(:wiki)
+ user = double(:user)
+
+ expect(Banzai::ObjectRenderer).to receive(:new).
+ with(project, user,
+ requested_path: 'foo',
+ project_wiki: wiki,
+ ref: 'bar',
+ pipeline: :note).
+ and_call_original
+
+ expect_any_instance_of(Banzai::ObjectRenderer).
+ to receive(:render).with([note], :note)
+
+ described_class.render([note], project, user, 'foo', wiki, 'bar')
+ end
+ end
+end
diff --git a/spec/lib/banzai/object_renderer_spec.rb b/spec/lib/banzai/object_renderer_spec.rb
new file mode 100644
index 00000000000..bcdb95250ca
--- /dev/null
+++ b/spec/lib/banzai/object_renderer_spec.rb
@@ -0,0 +1,146 @@
+require 'spec_helper'
+
+describe Banzai::ObjectRenderer do
+ let(:project) { create(:empty_project) }
+ let(:user) { project.owner }
+
+ describe '#render' do
+ it 'renders and redacts an Array of objects' do
+ renderer = described_class.new(project, user)
+ object = double(:object, note: 'hello', note_html: nil)
+
+ expect(renderer).to receive(:render_objects).with([object], :note).
+ and_call_original
+
+ expect(renderer).to receive(:redact_documents).
+ with(an_instance_of(Array)).
+ and_call_original
+
+ expect(object).to receive(:note_html=).with('<p>hello</p>')
+ expect(object).to receive(:user_visible_reference_count=).with(0)
+
+ renderer.render([object], :note)
+ end
+ end
+
+ describe '#render_objects' do
+ it 'renders an Array of objects' do
+ object = double(:object, note: 'hello')
+
+ renderer = described_class.new(project, user)
+
+ expect(renderer).to receive(:render_attributes).with([object], :note).
+ and_call_original
+
+ rendered = renderer.render_objects([object], :note)
+
+ expect(rendered).to be_an_instance_of(Array)
+ expect(rendered[0]).to be_an_instance_of(Nokogiri::HTML::DocumentFragment)
+ end
+ end
+
+ describe '#redact_documents' do
+ it 'redacts a set of documents and returns them as an Array of Hashes' do
+ doc = Nokogiri::HTML.fragment('<p>hello</p>')
+ renderer = described_class.new(project, user)
+
+ expect_any_instance_of(Banzai::Redactor).to receive(:redact).
+ with([doc]).
+ and_call_original
+
+ redacted = renderer.redact_documents([doc])
+
+ expect(redacted.count).to eq(1)
+ expect(redacted.first[:visible_reference_count]).to eq(0)
+ expect(redacted.first[:document].to_html).to eq('<p>hello</p>')
+ end
+ end
+
+ describe '#context_for' do
+ let(:object) { double(:object, note: 'hello') }
+ let(:renderer) { described_class.new(project, user) }
+
+ it 'returns a Hash' do
+ expect(renderer.context_for(object, :note)).to be_an_instance_of(Hash)
+ end
+
+ it 'includes the cache key' do
+ context = renderer.context_for(object, :note)
+
+ expect(context[:cache_key]).to eq([object, :note])
+ end
+
+ context 'when the object responds to "author"' do
+ it 'includes the author in the context' do
+ expect(object).to receive(:author).and_return('Alice')
+
+ context = renderer.context_for(object, :note)
+
+ expect(context[:author]).to eq('Alice')
+ end
+ end
+
+ context 'when the object does not respond to "author"' do
+ it 'does not include the author in the context' do
+ context = renderer.context_for(object, :note)
+
+ expect(context.key?(:author)).to eq(false)
+ end
+ end
+ end
+
+ describe '#render_attributes' do
+ it 'renders the attribute of a list of objects' do
+ objects = [double(:doc, note: 'hello'), double(:doc, note: 'bye')]
+ renderer = described_class.new(project, user, pipeline: :note)
+
+ expect(Banzai).to receive(:cache_collection_render).
+ with([
+ { text: 'hello', context: renderer.context_for(objects[0], :note) },
+ { text: 'bye', context: renderer.context_for(objects[1], :note) }
+ ]).
+ and_call_original
+
+ docs = renderer.render_attributes(objects, :note)
+
+ expect(docs[0]).to be_an_instance_of(Nokogiri::HTML::DocumentFragment)
+ expect(docs[0].to_html).to eq('<p>hello</p>')
+
+ expect(docs[1]).to be_an_instance_of(Nokogiri::HTML::DocumentFragment)
+ expect(docs[1].to_html).to eq('<p>bye</p>')
+ end
+
+ it 'returns when no objects to render' do
+ objects = []
+ renderer = described_class.new(project, user, pipeline: :note)
+
+ expect(Banzai).to receive(:cache_collection_render).
+ with([]).
+ and_call_original
+
+ expect(renderer.render_attributes(objects, :note)).to eq([])
+ end
+ end
+
+ describe '#base_context' do
+ let(:context) do
+ described_class.new(project, user, pipeline: :note).base_context
+ end
+
+ it 'returns a Hash' do
+ expect(context).to be_an_instance_of(Hash)
+ end
+
+ it 'includes the custom attributes' do
+ expect(context[:pipeline]).to eq(:note)
+ end
+
+ it 'includes the current user' do
+ expect(context[:current_user]).to eq(user)
+ end
+
+ it 'includes the current project' do
+ expect(context[:project]).to eq(project)
+ end
+ end
+end
diff --git a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
index 72bc6a0b704..51c89ac4889 100644
--- a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
@@ -59,7 +59,6 @@ describe Banzai::Pipeline::WikiPipeline do
{ "when GitLab is hosted at a root URL" => '/',
"when GitLab is hosted at a relative URL" => '/nested/relative/gitlab' }.each do |test_name, relative_url_root|
-
context test_name do
before do
allow(Gitlab.config.gitlab).to receive(:relative_url_root).and_return(relative_url_root)
diff --git a/spec/lib/banzai/redactor_spec.rb b/spec/lib/banzai/redactor_spec.rb
new file mode 100644
index 00000000000..254657a881d
--- /dev/null
+++ b/spec/lib/banzai/redactor_spec.rb
@@ -0,0 +1,73 @@
+require 'spec_helper'
+
+describe Banzai::Redactor do
+ let(:user) { build(:user) }
+ let(:project) { build(:empty_project) }
+ let(:redactor) { described_class.new(project, user) }
+
+ describe '#redact' do
+ it 'redacts an Array of documents' do
+ doc1 = Nokogiri::HTML.
+ fragment('<a class="gfm" data-reference-type="issue">foo</a>')
+
+ doc2 = Nokogiri::HTML.
+ fragment('<a class="gfm" data-reference-type="issue">bar</a>')
+
+ expect(redactor).to receive(:nodes_visible_to_user).and_return([])
+
+ redacted_data = redactor.redact([doc1, doc2])
+
+ expect(redacted_data.map { |data| data[:document] }).to eq([doc1, doc2])
+ expect(redacted_data.map { |data| data[:visible_reference_count] }).to eq([0, 0])
+ expect(doc1.to_html).to eq('foo')
+ expect(doc2.to_html).to eq('bar')
+ end
+
+ it 'does not redact an Array of documents' do
+ doc1_html = '<a class="gfm" data-reference-type="issue">foo</a>'
+ doc1 = Nokogiri::HTML.fragment(doc1_html)
+
+ doc2_html = '<a class="gfm" data-reference-type="issue">bar</a>'
+ doc2 = Nokogiri::HTML.fragment(doc2_html)
+
+ nodes = redactor.document_nodes([doc1, doc2]).map { |x| x[:nodes] }
+ expect(redactor).to receive(:nodes_visible_to_user).and_return(nodes.flatten)
+
+ redacted_data = redactor.redact([doc1, doc2])
+
+ expect(redacted_data.map { |data| data[:document] }).to eq([doc1, doc2])
+ expect(redacted_data.map { |data| data[:visible_reference_count] }).to eq([1, 1])
+ expect(doc1.to_html).to eq(doc1_html)
+ expect(doc2.to_html).to eq(doc2_html)
+ end
+ end
+
+ describe '#redact_nodes' do
+ it 'redacts an Array of nodes' do
+ doc = Nokogiri::HTML.fragment('<a href="foo">foo</a>')
+ node = doc.children[0]
+
+ expect(redactor).to receive(:nodes_visible_to_user).
+ with([node]).
+ and_return(Set.new)
+
+ redactor.redact_document_nodes([{ document: doc, nodes: [node] }])
+
+ expect(doc.to_html).to eq('foo')
+ end
+ end
+
+ describe '#nodes_visible_to_user' do
+ it 'returns a Set containing the visible nodes' do
+ doc = Nokogiri::HTML.fragment('<a data-reference-type="issue"></a>')
+ node = doc.children[0]
+
+ expect_any_instance_of(Banzai::ReferenceParser::IssueParser).
+ to receive(:nodes_visible_to_user).
+ with(user, [node]).
+ and_return([node])
+
+ expect(redactor.nodes_visible_to_user([node])).to eq(Set.new([node]))
+ end
+ end
+end
diff --git a/spec/lib/banzai/reference_parser/base_parser_spec.rb b/spec/lib/banzai/reference_parser/base_parser_spec.rb
index 543b4786d84..ac9c66e2663 100644
--- a/spec/lib/banzai/reference_parser/base_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/base_parser_spec.rb
@@ -234,4 +234,79 @@ describe Banzai::ReferenceParser::BaseParser, lib: true do
to eq([project])
end
end
+
+ describe '#collection_objects_for_ids' do
+ context 'with RequestStore disabled' do
+ it 'queries the collection directly' do
+ collection = User.all
+
+ expect(collection).to receive(:where).twice.and_call_original
+
+ 2.times do
+ expect(subject.collection_objects_for_ids(collection, [user.id])).
+ to eq([user])
+ end
+ end
+ end
+
+ context 'with RequestStore enabled' do
+ before do
+ cache = Hash.new { |hash, key| hash[key] = {} }
+
+ allow(RequestStore).to receive(:active?).and_return(true)
+ allow(subject).to receive(:collection_cache).and_return(cache)
+ end
+
+ it 'queries the collection on the first call' do
+ expect(subject.collection_objects_for_ids(User, [user.id])).
+ to eq([user])
+ end
+
+ it 'does not query previously queried objects' do
+ collection = User.all
+
+ expect(collection).to receive(:where).once.and_call_original
+
+ 2.times do
+ expect(subject.collection_objects_for_ids(collection, [user.id])).
+ to eq([user])
+ end
+ end
+
+ it 'casts String based IDs to Fixnums before querying objects' do
+ 2.times do
+ expect(subject.collection_objects_for_ids(User, [user.id.to_s])).
+ to eq([user])
+ end
+ end
+
+ it 'queries any additional objects after the first call' do
+ other_user = create(:user)
+
+ expect(subject.collection_objects_for_ids(User, [user.id])).
+ to eq([user])
+
+ expect(subject.collection_objects_for_ids(User, [user.id, other_user.id])).
+ to eq([user, other_user])
+ end
+
+ it 'caches objects on a per collection class basis' do
+ expect(subject.collection_objects_for_ids(User, [user.id])).
+ to eq([user])
+
+ expect(subject.collection_objects_for_ids(Project, [project.id])).
+ to eq([project])
+ end
+ end
+ end
+
+ describe '#collection_cache_key' do
+ it 'returns the cache key for a Class' do
+ expect(subject.collection_cache_key(Project)).to eq(Project)
+ end
+
+ it 'returns the cache key for an ActiveRecord::Relation' do
+ expect(subject.collection_cache_key(Project.all)).to eq(Project)
+ end
+ end
end
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 d562d8b25ea..d20fd4ab7dd 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -19,19 +19,18 @@ module Ci
expect(config_processor.builds_for_stage_and_ref(type, "master").first).to eq({
stage: "test",
stage_idx: 1,
- except: nil,
name: :rspec,
- only: nil,
commands: "pwd\nrspec",
tag_list: [],
options: {},
allow_failure: false,
when: "on_success",
environment: nil,
+ yaml_variables: []
})
end
- describe :only do
+ describe 'only' do
it "does not return builds if only has another branch" do
config = YAML.dump({
before_script: ["pwd"],
@@ -143,7 +142,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"] },
@@ -188,7 +186,7 @@ module Ci
end
end
- describe :except do
+ describe 'except' do
it "returns builds if except has another branch" do
config = YAML.dump({
before_script: ["pwd"],
@@ -433,11 +431,9 @@ module Ci
expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1)
expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
- except: nil,
stage: "test",
stage_idx: 1,
name: :rspec,
- only: nil,
commands: "pwd\nrspec",
tag_list: [],
options: {
@@ -447,6 +443,7 @@ module Ci
allow_failure: false,
when: "on_success",
environment: nil,
+ yaml_variables: []
})
end
@@ -462,11 +459,9 @@ module Ci
expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1)
expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
- except: nil,
stage: "test",
stage_idx: 1,
name: :rspec,
- only: nil,
commands: "pwd\nrspec",
tag_list: [],
options: {
@@ -476,101 +471,126 @@ module Ci
allow_failure: false,
when: "on_success",
environment: nil,
+ yaml_variables: []
})
end
end
describe 'Variables' do
- context 'when global variables are defined' do
- it 'returns global variables' do
- variables = {
- VAR1: 'value1',
- VAR2: 'value2',
- }
+ let(:config_processor) { GitlabCiYamlProcessor.new(YAML.dump(config), path) }
- config = YAML.dump({
+ subject { config_processor.builds.first[:yaml_variables] }
+
+ context 'when global variables are defined' do
+ let(:variables) do
+ { VAR1: 'value1', VAR2: 'value2' }
+ end
+ let(:config) do
+ {
variables: variables,
before_script: ['pwd'],
rspec: { script: 'rspec' }
- })
+ }
+ end
- config_processor = GitlabCiYamlProcessor.new(config, path)
+ it 'returns global variables' do
+ expect(subject).to contain_exactly(
+ { key: :VAR1, value: 'value1', public: true },
+ { key: :VAR2, value: 'value2', public: true }
+ )
+ end
+ end
- expect(config_processor.global_variables).to eq(variables)
+ context 'when job and global variables are defined' do
+ let(:global_variables) do
+ { VAR1: 'global1', VAR3: 'global3' }
+ end
+ let(:job_variables) do
+ { VAR1: 'value1', VAR2: 'value2' }
+ end
+ let(:config) do
+ {
+ before_script: ['pwd'],
+ variables: global_variables,
+ rspec: { script: 'rspec', variables: job_variables }
+ }
+ end
+
+ it 'returns all unique variables' do
+ expect(subject).to contain_exactly(
+ { key: :VAR3, value: 'global3', public: true },
+ { key: :VAR1, value: 'value1', public: true },
+ { key: :VAR2, value: 'value2', public: true }
+ )
end
end
context 'when job variables are defined' do
- context 'when syntax is correct' do
- it 'returns job variables' do
- variables = {
- KEY1: 'value1',
- SOME_KEY_2: 'value2'
- }
+ let(:config) do
+ {
+ before_script: ['pwd'],
+ rspec: { script: 'rspec', variables: variables }
+ }
+ end
- config = YAML.dump(
- { before_script: ['pwd'],
- rspec: {
- variables: variables,
- script: 'rspec' }
- })
+ context 'when also global variables are defined' do
- config_processor = GitlabCiYamlProcessor.new(config, path)
+ end
+
+ context 'when syntax is correct' do
+ let(:variables) do
+ { VAR1: 'value1', VAR2: 'value2' }
+ end
- expect(config_processor.job_variables(:rspec)).to eq variables
+ it 'returns job variables' do
+ expect(subject).to contain_exactly(
+ { key: :VAR1, value: 'value1', public: true },
+ { key: :VAR2, value: 'value2', public: true }
+ )
end
end
context 'when syntax is incorrect' do
context 'when variables defined but invalid' do
- it 'raises error' do
- variables = [:KEY1, 'value1', :KEY2, 'value2']
-
- config = YAML.dump(
- { before_script: ['pwd'],
- rspec: {
- variables: variables,
- script: 'rspec' }
- })
+ let(:variables) do
+ [ :VAR1, 'value1', :VAR2, 'value2' ]
+ end
- expect { GitlabCiYamlProcessor.new(config, path) }
+ it 'raises error' do
+ expect { subject }
.to raise_error(GitlabCiYamlProcessor::ValidationError,
- /job: variables should be a map/)
+ /job: variables should be a map/)
end
end
context 'when variables key defined but value not specified' do
- it 'returns empty array' do
- config = YAML.dump(
- { before_script: ['pwd'],
- rspec: {
- variables: nil,
- script: 'rspec' }
- })
-
- config_processor = GitlabCiYamlProcessor.new(config, path)
+ let(:variables) do
+ nil
+ end
+ it 'returns empty array' do
##
- # 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
+ expect(subject).to be_an_instance_of(Array)
+ expect(subject).to be_empty
end
end
end
end
context 'when job variables are not defined' do
- it 'returns empty array' do
- config = YAML.dump({
+ let(:config) do
+ {
before_script: ['pwd'],
rspec: { script: 'rspec' }
- })
-
- config_processor = GitlabCiYamlProcessor.new(config, path)
+ }
+ end
- expect(config_processor.job_variables(:rspec)).to eq []
+ it 'returns empty array' do
+ expect(subject).to be_an_instance_of(Array)
+ expect(subject).to be_empty
end
end
end
@@ -591,7 +611,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' },
@@ -669,11 +702,9 @@ module Ci
expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1)
expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
- except: nil,
stage: "test",
stage_idx: 1,
name: :rspec,
- only: nil,
commands: "pwd\nrspec",
tag_list: [],
options: {
@@ -689,6 +720,7 @@ module Ci
when: "on_success",
allow_failure: false,
environment: nil,
+ yaml_variables: []
})
end
@@ -807,17 +839,16 @@ module Ci
it "doesn't create jobs that start with dot" do
expect(subject.size).to eq(1)
expect(subject.first).to eq({
- except: nil,
stage: "test",
stage_idx: 1,
name: :normal_job,
- only: nil,
commands: "test",
tag_list: [],
options: {},
when: "on_success",
allow_failure: false,
environment: nil,
+ yaml_variables: []
})
end
end
@@ -853,30 +884,28 @@ module Ci
it "is correctly supported for jobs" do
expect(subject.size).to eq(2)
expect(subject.first).to eq({
- except: nil,
stage: "build",
stage_idx: 0,
name: :job1,
- only: nil,
commands: "execute-script-for-job",
tag_list: [],
options: {},
when: "on_success",
allow_failure: false,
environment: nil,
+ yaml_variables: []
})
expect(subject.second).to eq({
- except: nil,
stage: "build",
stage_idx: 0,
name: :job2,
- only: nil,
commands: "execute-script-for-job",
tag_list: [],
options: {},
when: "on_success",
allow_failure: false,
environment: nil,
+ yaml_variables: []
})
end
end
@@ -951,7 +980,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 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 +994,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 +1008,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 +1036,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,38 +1110,38 @@ 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
config = YAML.dump({ rspec: { script: "test", when: 1 } })
expect do
GitlabCiYamlProcessor.new(config, path)
- end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: when parameter should be on_success, on_failure or always")
+ end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: when parameter should be on_success, on_failure, always or manual")
end
it "returns errors if job artifacts:name is not an a string" do
@@ -1161,21 +1190,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 +1235,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/container_registry/blob_spec.rb b/spec/lib/container_registry/blob_spec.rb
index 4d8cb787dde..bbacdc67ebd 100644
--- a/spec/lib/container_registry/blob_spec.rb
+++ b/spec/lib/container_registry/blob_spec.rb
@@ -9,8 +9,9 @@ describe ContainerRegistry::Blob do
'size' => 1000
}
end
+ let(:token) { 'authorization-token' }
- let(:registry) { ContainerRegistry::Registry.new('http://example.com') }
+ let(:registry) { ContainerRegistry::Registry.new('http://example.com', token: token) }
let(:repository) { registry.repository('group/test') }
let(:blob) { repository.blob(config) }
@@ -58,4 +59,53 @@ describe ContainerRegistry::Blob do
it { is_expected.to be_truthy }
end
+
+ context '#data' do
+ let(:data) { '{"key":"value"}' }
+
+ subject { blob.data }
+
+ context 'when locally stored' do
+ before do
+ stub_request(:get, 'http://example.com/v2/group/test/blobs/sha256:0123456789012345').
+ to_return(
+ status: 200,
+ headers: { 'Content-Type' => 'application/json' },
+ body: data)
+ end
+
+ it { is_expected.to eq(data) }
+ end
+
+ context 'when externally stored' do
+ before do
+ stub_request(:get, 'http://example.com/v2/group/test/blobs/sha256:0123456789012345').
+ with(headers: { 'Authorization' => "bearer #{token}" }).
+ to_return(
+ status: 307,
+ headers: { 'Location' => location })
+ end
+
+ context 'for a valid address' do
+ let(:location) { 'http://external.com/blob/file' }
+
+ before do
+ stub_request(:get, location).
+ with(headers: { 'Authorization' => nil }).
+ to_return(
+ status: 200,
+ headers: { 'Content-Type' => 'application/json' },
+ body: data)
+ end
+
+ it { is_expected.to eq(data) }
+ end
+
+ context 'for invalid file' do
+ let(:location) { 'file:///etc/passwd' }
+
+ it { expect{ subject }.to raise_error(ArgumentError, 'invalid address') }
+ end
+ end
+ end
end
diff --git a/spec/lib/container_registry/tag_spec.rb b/spec/lib/container_registry/tag_spec.rb
index c7324c2bf77..c5e31ae82b6 100644
--- a/spec/lib/container_registry/tag_spec.rb
+++ b/spec/lib/container_registry/tag_spec.rb
@@ -77,24 +77,47 @@ describe ContainerRegistry::Tag do
end
context 'config processing' do
- before do
- stub_request(:get, 'http://example.com/v2/group/test/blobs/sha256:d7a513a663c1a6dcdba9ed832ca53c02ac2af0c333322cd6ca92936d1d9917ac').
- with(headers: { 'Accept' => 'application/octet-stream' }).
- to_return(
- status: 200,
- body: File.read(Rails.root + 'spec/fixtures/container_registry/config_blob.json'))
- end
+ shared_examples 'a processable' do
+ context '#config' do
+ subject { tag.config }
- context '#config' do
- subject { tag.config }
+ it { is_expected.not_to be_nil }
+ end
+
+ context '#created_at' do
+ subject { tag.created_at }
- it { is_expected.not_to be_nil }
+ it { is_expected.not_to be_nil }
+ end
end
- context '#created_at' do
- subject { tag.created_at }
+ context 'when locally stored' do
+ before do
+ stub_request(:get, 'http://example.com/v2/group/test/blobs/sha256:d7a513a663c1a6dcdba9ed832ca53c02ac2af0c333322cd6ca92936d1d9917ac').
+ with(headers: { 'Accept' => 'application/octet-stream' }).
+ to_return(
+ status: 200,
+ body: File.read(Rails.root + 'spec/fixtures/container_registry/config_blob.json'))
+ end
+
+ it_behaves_like 'a processable'
+ end
- it { is_expected.not_to be_nil }
+ context 'when externally stored' do
+ before do
+ stub_request(:get, 'http://example.com/v2/group/test/blobs/sha256:d7a513a663c1a6dcdba9ed832ca53c02ac2af0c333322cd6ca92936d1d9917ac').
+ with(headers: { 'Accept' => 'application/octet-stream' }).
+ to_return(
+ status: 307,
+ headers: { 'Location' => 'http://external.com/blob/file' })
+
+ stub_request(:get, 'http://external.com/blob/file').
+ to_return(
+ status: 200,
+ body: File.read(Rails.root + 'spec/fixtures/container_registry/config_blob.json'))
+ end
+
+ it_behaves_like 'a processable'
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/badge/build_spec.rb b/spec/lib/gitlab/badge/build_spec.rb
index 2034445a197..f3b522a02f5 100644
--- a/spec/lib/gitlab/badge/build_spec.rb
+++ b/spec/lib/gitlab/badge/build_spec.rb
@@ -113,7 +113,7 @@ describe Gitlab::Badge::Build do
sha: sha,
ref: branch)
- create(:ci_build, pipeline: pipeline)
+ create(:ci_build, pipeline: pipeline, stage: 'notify')
end
def status_node(data, status)
diff --git a/spec/lib/gitlab/bitbucket_import/client_spec.rb b/spec/lib/gitlab/bitbucket_import/client_spec.rb
index 760d66a1488..7543c29bcc4 100644
--- a/spec/lib/gitlab/bitbucket_import/client_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/client_spec.rb
@@ -54,12 +54,12 @@ describe Gitlab::BitbucketImport::Client, lib: true do
context 'project import' do
it 'calls .from_project with no errors' do
project = create(:empty_project)
+ project.import_url = "ssh://git@bitbucket.org/test/test.git"
project.create_or_update_import_data(credentials:
{ user: "git",
password: nil,
bb_session: { bitbucket_access_token: "test",
bitbucket_access_token_secret: "test" } })
- project.import_url = "ssh://git@bitbucket.org/test/test.git"
expect { described_class.from_project(project) }.not_to raise_error
end
diff --git a/spec/lib/gitlab/build_data_builder_spec.rb b/spec/lib/gitlab/build_data_builder_spec.rb
index 38be9448794..23ae5cfacc4 100644
--- a/spec/lib/gitlab/build_data_builder_spec.rb
+++ b/spec/lib/gitlab/build_data_builder_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe 'Gitlab::BuildDataBuilder' do
let(:build) { create(:ci_build) }
- describe :build do
+ describe '.build' do
let(:data) do
Gitlab::BuildDataBuilder.build(build)
end
diff --git a/spec/lib/gitlab/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 47c68f96dc8..c468ecf957b 100644
--- a/spec/lib/gitlab/ci/config/node/configurable_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/configurable_spec.rb
@@ -7,26 +7,58 @@ describe Gitlab::Ci::Config::Node::Configurable do
node.include(described_class)
end
- describe 'allowed nodes' do
+ 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
- describe '#allowed_nodes' do
- it 'has valid allowed nodes' do
- expect(node.allowed_nodes).to include :object
+ describe '.nodes' do
+ it 'has valid nodes' do
+ expect(node.nodes).to include :object
end
it 'creates a node factory' do
- expect(node.allowed_nodes[:object])
+ expect(node.nodes[:object])
.to be_an_instance_of Gitlab::Ci::Config::Node::Factory
end
it 'returns a duplicated factory object' do
- first_factory = node.allowed_nodes[:object]
- second_factory = node.allowed_nodes[:object]
+ first_factory = node.nodes[:object]
+ second_factory = node.nodes[:object]
expect(first_factory).not_to be_equal(second_factory)
end
diff --git a/spec/lib/gitlab/ci/config/node/factory_spec.rb b/spec/lib/gitlab/ci/config/node/factory_spec.rb
index d681aa32456..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,13 +21,35 @@ 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
+
+ context 'when setting key' do
+ it 'creates entry with custom key' do
+ entry = factory
+ .with(value: ['ls', 'pwd'], key: 'test key')
+ .create!
+
+ 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
@@ -35,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 b1972172435..c87c9e97bc8 100644
--- a/spec/lib/gitlab/ci/config/node/global_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/global_spec.rb
@@ -3,61 +3,173 @@ require 'spec_helper'
describe Gitlab::Ci::Config::Node::Global do
let(:global) { described_class.new(hash) }
- describe '#allowed_nodes' do
+ describe '.nodes' do
it 'can contain global config keys' do
- expect(global.allowed_nodes).to include :before_script
+ expect(described_class.nodes).to include :before_script
end
it 'returns a hash' do
- expect(global.allowed_nodes).to be_a Hash
+ expect(described_class.nodes).to be_a Hash
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
+ end
- it 'creates nodes hash' do
- expect(global.nodes).to be_an Array
+ 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
@@ -79,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 should be an array of strings'
+ .to include 'before_script config should be an array of strings'
end
end
@@ -100,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 e4d6481f8a5..ee7395362a9 100644
--- a/spec/lib/gitlab/ci/config/node/script_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/script_spec.rb
@@ -1,17 +1,17 @@
require 'spec_helper'
describe Gitlab::Ci::Config::Node::Script do
- let(:entry) { described_class.new(value) }
+ let(:entry) { described_class.new(config) }
- describe '#validate!' do
- before { entry.validate! }
+ describe '#process!' do
+ before { entry.process! }
- context 'when entry value is correct' do
- let(:value) { ['ls', 'pwd'] }
+ context 'when entry config value is correct' 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
@@ -29,12 +29,12 @@ describe Gitlab::Ci::Config::Node::Script do
end
context 'when entry value is not correct' do
- let(:value) { 'ls' }
+ let(:config) { 'ls' }
describe '#errors' do
it 'saves errors' do
expect(entry.errors)
- .to include /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/validatable_spec.rb b/spec/lib/gitlab/ci/config/node/validatable_spec.rb
new file mode 100644
index 00000000000..10cd01afcd1
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/node/validatable_spec.rb
@@ -0,0 +1,50 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Node::Validatable do
+ let(:node) { Class.new }
+
+ before do
+ node.include(described_class)
+ end
+
+ describe '.validator' do
+ before do
+ node.class_eval do
+ attr_accessor :test_attribute
+
+ validations do
+ validates :test_attribute, presence: true
+ end
+ end
+ end
+
+ it 'returns validator' do
+ expect(node.validator.superclass)
+ .to be Gitlab::Ci::Config::Node::Validator
+ end
+
+ context 'when validating node instance' do
+ let(:node_instance) { node.new }
+
+ context 'when attribute is valid' do
+ before do
+ node_instance.test_attribute = 'valid'
+ end
+
+ it 'instance of validator is valid' do
+ expect(node.validator.new(node_instance)).to be_valid
+ end
+ end
+
+ context 'when attribute is not valid' do
+ before do
+ node_instance.test_attribute = nil
+ end
+
+ it 'instance of validator is invalid' do
+ expect(node.validator.new(node_instance)).to be_invalid
+ end
+ end
+ 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
new file mode 100644
index 00000000000..090fd63b844
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/node/validator_spec.rb
@@ -0,0 +1,55 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Node::Validator do
+ let(:validator) { Class.new(described_class) }
+ let(:validator_instance) { validator.new(node) }
+ let(:node) { spy('node') }
+
+ 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')
+ end
+
+ it 'validates attribute in node' do
+ expect(node).to receive(:test_attribute)
+ expect(validator_instance).to be_valid
+ end
+
+ it 'returns no errors' do
+ validator_instance.validate
+
+ expect(validator_instance.messages).to be_empty
+ end
+ end
+
+ context 'when node is invalid' do
+ before do
+ allow(node).to receive(:test_attribute).and_return(nil)
+ end
+
+ it 'validates attribute in node' do
+ expect(node).to receive(:test_attribute)
+ expect(validator_instance).to be_invalid
+ end
+
+ it 'returns errors' do
+ validator_instance.validate
+
+ expect(validator_instance.messages)
+ .to include "node test attribute can't be blank"
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/node/variables_spec.rb b/spec/lib/gitlab/ci/config/node/variables_spec.rb
new file mode 100644
index 00000000000..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 3871d939feb..bc5a5e43103 100644
--- a/spec/lib/gitlab/ci/config_spec.rb
+++ b/spec/lib/gitlab/ci/config_spec.rb
@@ -40,32 +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
+ end
- it 'has errors' do
- expect(config.errors).not_to be_empty
- 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/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index 9096ad101b0..4ec3f19e03f 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -13,6 +13,10 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
context 'outside a transaction' do
before do
expect(model).to receive(:transaction_open?).and_return(false)
+
+ unless Gitlab::Database.postgresql?
+ allow_any_instance_of(Gitlab::Database::MigrationHelpers).to receive(:disable_statement_timeout)
+ end
end
context 'using PostgreSQL' do
diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb
index a0cbef6e6a4..e883a6eb9c2 100644
--- a/spec/lib/gitlab/diff/file_spec.rb
+++ b/spec/lib/gitlab/diff/file_spec.rb
@@ -6,16 +6,16 @@ describe Gitlab::Diff::File, lib: true do
let(:project) { create(:project) }
let(:commit) { project.commit(sample_commit.id) }
let(:diff) { commit.diffs.first }
- let(:diff_file) { Gitlab::Diff::File.new(diff, [commit.parent, commit]) }
+ let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: project.repository) }
- describe :diff_lines do
+ describe '#diff_lines' do
let(:diff_lines) { diff_file.diff_lines }
it { expect(diff_lines.size).to eq(30) }
it { expect(diff_lines.first).to be_kind_of(Gitlab::Diff::Line) }
end
- describe :mode_changed? do
+ describe '#mode_changed?' do
it { expect(diff_file.mode_changed?).to be_falsey }
end
@@ -32,4 +32,18 @@ describe Gitlab::Diff::File, lib: true do
expect(diff_file.too_large?).to eq(false)
end
end
+
+ describe '#collapsed?' do
+ it 'returns true for a file that is quite big' do
+ expect(diff).to receive(:collapsed?).and_return(true)
+
+ expect(diff_file.collapsed?).to eq(true)
+ end
+
+ it 'returns false for a file that is small enough' do
+ expect(diff).to receive(:collapsed?).and_return(false)
+
+ expect(diff_file.collapsed?).to eq(false)
+ end
+ end
end
diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb
index d19bf4ac84b..88e4115c453 100644
--- a/spec/lib/gitlab/diff/highlight_spec.rb
+++ b/spec/lib/gitlab/diff/highlight_spec.rb
@@ -6,11 +6,11 @@ describe Gitlab::Diff::Highlight, lib: true do
let(:project) { create(:project) }
let(:commit) { project.commit(sample_commit.id) }
let(:diff) { commit.diffs.first }
- let(:diff_file) { Gitlab::Diff::File.new(diff, [commit.parent, commit]) }
+ let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: project.repository) }
describe '#highlight' do
context "with a diff file" do
- let(:subject) { Gitlab::Diff::Highlight.new(diff_file).highlight }
+ let(:subject) { Gitlab::Diff::Highlight.new(diff_file, repository: project.repository).highlight }
it 'should return Gitlab::Diff::Line elements' do
expect(subject.first).to be_an_instance_of(Gitlab::Diff::Line)
@@ -28,20 +28,20 @@ describe Gitlab::Diff::Highlight, lib: true do
end
it 'highlights and marks removed lines' do
- code = %Q{-<span id="LC9" class="line"> <span class="k">raise</span> <span class="s2">&quot;System commands must be given as an array of strings&quot;</span></span>\n}
+ code = %Q{-<span id="LC9" class="line"> <span class="k">raise</span> <span class="s2">"System commands must be given as an array of strings"</span></span>\n}
expect(subject[4].text).to eq(code)
end
it 'highlights and marks added lines' do
- code = %Q{+<span id="LC9" class="line"> <span class="k">raise</span> <span class="no"><span class='idiff left'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff right'> </span><span class="s2">&quot;System commands must be given as an array of strings&quot;</span></span>\n}
+ code = %Q{+<span id="LC9" class="line"> <span class="k">raise</span> <span class="no"><span class='idiff left'>RuntimeError</span></span><span class="p"><span class='idiff'>,</span></span><span class='idiff right'> </span><span class="s2">"System commands must be given as an array of strings"</span></span>\n}
expect(subject[5].text).to eq(code)
end
end
context "with diff lines" do
- let(:subject) { Gitlab::Diff::Highlight.new(diff_file.diff_lines).highlight }
+ let(:subject) { Gitlab::Diff::Highlight.new(diff_file.diff_lines, repository: project.repository).highlight }
it 'should return Gitlab::Diff::Line elements' do
expect(subject.first).to be_an_instance_of(Gitlab::Diff::Line)
diff --git a/spec/lib/gitlab/diff/inline_diff_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">&#39;def&#39;</span>}.html_safe }
diff --git a/spec/lib/gitlab/diff/inline_diff_spec.rb b/spec/lib/gitlab/diff/inline_diff_spec.rb
index 95a993d26cf..8ca3f73509e 100644
--- a/spec/lib/gitlab/diff/inline_diff_spec.rb
+++ b/spec/lib/gitlab/diff/inline_diff_spec.rb
@@ -3,14 +3,19 @@ require 'spec_helper'
describe Gitlab::Diff::InlineDiff, lib: true do
describe '.for_lines' do
let(:diff) do
- <<eos
- class Test
-- def initialize(test = true)
-+ def initialize(test = false)
- @test = test
- end
- end
-eos
+ <<-EOF.strip_heredoc
+ class Test
+ - def initialize(test = true)
+ + def initialize(test = false)
+ @test = test
+ - if true
+ - @foo = "bar"
+ + unless false
+ + @foo = "baz"
+ end
+ end
+ end
+ EOF
end
let(:subject) { described_class.for_lines(diff.lines) }
@@ -20,8 +25,11 @@ eos
expect(subject[1]).to eq([25..27])
expect(subject[2]).to eq([25..28])
expect(subject[3]).to be_nil
- expect(subject[4]).to be_nil
- expect(subject[5]).to be_nil
+ expect(subject[4]).to eq([5..10])
+ expect(subject[5]).to eq([17..17])
+ expect(subject[6]).to eq([5..15])
+ expect(subject[7]).to eq([17..17])
+ expect(subject[8]).to be_nil
end
end
diff --git a/spec/lib/gitlab/diff/line_mapper_spec.rb b/spec/lib/gitlab/diff/line_mapper_spec.rb
new file mode 100644
index 00000000000..4e50e03bb7e
--- /dev/null
+++ b/spec/lib/gitlab/diff/line_mapper_spec.rb
@@ -0,0 +1,137 @@
+require 'spec_helper'
+
+describe Gitlab::Diff::LineMapper, lib: true do
+ include RepoHelpers
+
+ let(:project) { create(:project) }
+ let(:repository) { project.repository }
+ let(:commit) { project.commit(sample_commit.id) }
+ let(:diffs) { commit.diffs }
+ let(:diff) { diffs.first }
+ let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: repository) }
+ subject { described_class.new(diff_file) }
+
+ describe '#old_to_new' do
+ context "with a diff file" do
+ let(:mapping) do
+ {
+ 1 => 1,
+ 2 => 2,
+ 3 => 3,
+ 4 => 4,
+ 5 => 5,
+ 6 => 6,
+ 7 => 7,
+ 8 => 8,
+ 9 => nil,
+ # nil => 9,
+ 10 => 10,
+ 11 => 11,
+ 12 => 12,
+ 13 => nil,
+ 14 => nil,
+ # nil => 15,
+ # nil => 16,
+ # nil => 17,
+ # nil => 18,
+ # nil => 19,
+ # nil => 20,
+ 15 => 21,
+ 16 => 22,
+ 17 => 23,
+ 18 => 24,
+ 19 => 25,
+ 20 => 26,
+ 21 => 27,
+ # nil => 28,
+ 22 => 29,
+ 23 => 30,
+ 24 => 31,
+ 25 => 32,
+ 26 => 33,
+ 27 => 34,
+ 28 => 35,
+ 29 => 36,
+ 30 => 37
+ }
+ end
+
+ it 'returns the new line number for the old line number' do
+ mapping.each do |old_line, new_line|
+ expect(subject.old_to_new(old_line)).to eq(new_line)
+ end
+ end
+ end
+
+ context "without a diff file" do
+ let(:diff_file) { nil }
+
+ it "returns the same line number" do
+ expect(subject.old_to_new(100)).to eq(100)
+ end
+ end
+ end
+
+ describe '#new_to_old' do
+ context "with a diff file" do
+ let(:mapping) do
+ {
+ 1 => 1,
+ 2 => 2,
+ 3 => 3,
+ 4 => 4,
+ 5 => 5,
+ 6 => 6,
+ 7 => 7,
+ 8 => 8,
+ # nil => 9,
+ 9 => nil,
+ 10 => 10,
+ 11 => 11,
+ 12 => 12,
+ # nil => 13,
+ # nil => 14,
+ 13 => nil,
+ 14 => nil,
+ 15 => nil,
+ 16 => nil,
+ 17 => nil,
+ 18 => nil,
+ 19 => nil,
+ 20 => nil,
+ 21 => 15,
+ 22 => 16,
+ 23 => 17,
+ 24 => 18,
+ 25 => 19,
+ 26 => 20,
+ 27 => 21,
+ 28 => nil,
+ 29 => 22,
+ 30 => 23,
+ 31 => 24,
+ 32 => 25,
+ 33 => 26,
+ 34 => 27,
+ 35 => 28,
+ 36 => 29,
+ 37 => 30
+ }
+ end
+
+ it 'returns the old line number for the new line number' do
+ mapping.each do |new_line, old_line|
+ expect(subject.new_to_old(new_line)).to eq(old_line)
+ end
+ end
+ end
+
+ context "without a diff file" do
+ let(:diff_file) { nil }
+
+ it "returns the same line number" do
+ expect(subject.new_to_old(100)).to eq(100)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/diff/parallel_diff_spec.rb b/spec/lib/gitlab/diff/parallel_diff_spec.rb
index 1c5bbc47120..5f76b70c6f5 100644
--- a/spec/lib/gitlab/diff/parallel_diff_spec.rb
+++ b/spec/lib/gitlab/diff/parallel_diff_spec.rb
@@ -8,8 +8,7 @@ describe Gitlab::Diff::ParallelDiff, lib: true do
let(:commit) { project.commit(sample_commit.id) }
let(:diffs) { commit.diffs }
let(:diff) { diffs.first }
- let(:diff_refs) { [commit.parent, commit] }
- let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs) }
+ let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: repository) }
subject { described_class.new(diff_file) }
let(:parallel_diff_result_array) { YAML.load_file("#{Rails.root}/spec/fixtures/parallel_diff_result.yml") }
diff --git a/spec/lib/gitlab/diff/parser_spec.rb b/spec/lib/gitlab/diff/parser_spec.rb
index cdff063a9ed..c3359627652 100644
--- a/spec/lib/gitlab/diff/parser_spec.rb
+++ b/spec/lib/gitlab/diff/parser_spec.rb
@@ -8,7 +8,7 @@ describe Gitlab::Diff::Parser, lib: true do
let(:diff) { commit.diffs.first }
let(:parser) { Gitlab::Diff::Parser.new }
- describe :parse do
+ describe '#parse' do
let(:diff) do
<<eos
--- a/files/ruby/popen.rb
diff --git a/spec/lib/gitlab/diff/position_spec.rb b/spec/lib/gitlab/diff/position_spec.rb
new file mode 100644
index 00000000000..cf28628cb96
--- /dev/null
+++ b/spec/lib/gitlab/diff/position_spec.rb
@@ -0,0 +1,341 @@
+require 'spec_helper'
+
+describe Gitlab::Diff::Position, lib: true do
+ include RepoHelpers
+
+ let(:project) { create(:project) }
+
+ describe "position for an added file" do
+ let(:commit) { project.commit("2ea1f3dec713d940208fb5ce4a38765ecb5d3f73") }
+
+ subject do
+ described_class.new(
+ old_path: "files/images/wm.svg",
+ new_path: "files/images/wm.svg",
+ old_line: nil,
+ new_line: 5,
+ diff_refs: commit.diff_refs
+ )
+ end
+
+ describe "#diff_file" do
+ it "returns the correct diff file" do
+ diff_file = subject.diff_file(project.repository)
+
+ expect(diff_file.new_file).to be true
+ expect(diff_file.new_path).to eq(subject.new_path)
+ expect(diff_file.diff_refs).to eq(subject.diff_refs)
+ end
+ end
+
+ describe "#diff_line" do
+ it "returns the correct diff line" do
+ diff_line = subject.diff_line(project.repository)
+
+ expect(diff_line.added?).to be true
+ expect(diff_line.new_line).to eq(subject.new_line)
+ expect(diff_line.text).to eq("+ <desc>Created with Sketch.</desc>")
+ end
+ end
+
+ describe "#line_code" do
+ it "returns the correct line code" do
+ line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.new_line, 0)
+
+ expect(subject.line_code(project.repository)).to eq(line_code)
+ end
+ end
+ end
+
+ describe "position for a changed file" do
+ let(:commit) { project.commit("570e7b2abdd848b95f2f578043fc23bd6f6fd24d") }
+
+ describe "position for an added line" do
+ subject do
+ described_class.new(
+ old_path: "files/ruby/popen.rb",
+ new_path: "files/ruby/popen.rb",
+ old_line: nil,
+ new_line: 14,
+ diff_refs: commit.diff_refs
+ )
+ end
+
+ describe "#diff_file" do
+ it "returns the correct diff file" do
+ diff_file = subject.diff_file(project.repository)
+
+ expect(diff_file.old_path).to eq(subject.old_path)
+ expect(diff_file.new_path).to eq(subject.new_path)
+ expect(diff_file.diff_refs).to eq(subject.diff_refs)
+ end
+ end
+
+ describe "#diff_line" do
+ it "returns the correct diff line" do
+ diff_line = subject.diff_line(project.repository)
+
+ expect(diff_line.added?).to be true
+ expect(diff_line.new_line).to eq(subject.new_line)
+ expect(diff_line.text).to eq("+ vars = {")
+ end
+ end
+
+ describe "#line_code" do
+ it "returns the correct line code" do
+ line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.new_line, 15)
+
+ expect(subject.line_code(project.repository)).to eq(line_code)
+ end
+ end
+ end
+
+ describe "position for an unchanged line" do
+ subject do
+ described_class.new(
+ old_path: "files/ruby/popen.rb",
+ new_path: "files/ruby/popen.rb",
+ old_line: 16,
+ new_line: 22,
+ diff_refs: commit.diff_refs
+ )
+ end
+
+ describe "#diff_file" do
+ it "returns the correct diff file" do
+ diff_file = subject.diff_file(project.repository)
+
+ expect(diff_file.old_path).to eq(subject.old_path)
+ expect(diff_file.new_path).to eq(subject.new_path)
+ expect(diff_file.diff_refs).to eq(subject.diff_refs)
+ end
+ end
+
+ describe "#diff_line" do
+ it "returns the correct diff line" do
+ diff_line = subject.diff_line(project.repository)
+
+ expect(diff_line.unchanged?).to be true
+ expect(diff_line.old_line).to eq(subject.old_line)
+ expect(diff_line.new_line).to eq(subject.new_line)
+ expect(diff_line.text).to eq(" unless File.directory?(path)")
+ end
+ end
+
+ describe "#line_code" do
+ it "returns the correct line code" do
+ line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.new_line, subject.old_line)
+
+ expect(subject.line_code(project.repository)).to eq(line_code)
+ end
+ end
+ end
+
+ describe "position for a removed line" do
+ subject do
+ described_class.new(
+ old_path: "files/ruby/popen.rb",
+ new_path: "files/ruby/popen.rb",
+ old_line: 14,
+ new_line: nil,
+ diff_refs: commit.diff_refs
+ )
+ end
+
+ describe "#diff_file" do
+ it "returns the correct diff file" do
+ diff_file = subject.diff_file(project.repository)
+
+ expect(diff_file.old_path).to eq(subject.old_path)
+ expect(diff_file.new_path).to eq(subject.new_path)
+ expect(diff_file.diff_refs).to eq(subject.diff_refs)
+ end
+ end
+
+ describe "#diff_line" do
+ it "returns the correct diff line" do
+ diff_line = subject.diff_line(project.repository)
+
+ expect(diff_line.removed?).to be true
+ expect(diff_line.old_line).to eq(subject.old_line)
+ expect(diff_line.text).to eq("- options = { chdir: path }")
+ end
+ end
+
+ describe "#line_code" do
+ it "returns the correct line code" do
+ line_code = Gitlab::Diff::LineCode.generate(subject.file_path, 13, subject.old_line)
+
+ expect(subject.line_code(project.repository)).to eq(line_code)
+ end
+ end
+ end
+ end
+
+ describe "position for a renamed file" do
+ let(:commit) { project.commit("6907208d755b60ebeacb2e9dfea74c92c3449a1f") }
+
+ describe "position for an added line" do
+ subject do
+ described_class.new(
+ old_path: "files/js/commit.js.coffee",
+ new_path: "files/js/commit.coffee",
+ old_line: nil,
+ new_line: 4,
+ diff_refs: commit.diff_refs
+ )
+ end
+
+ describe "#diff_file" do
+ it "returns the correct diff file" do
+ diff_file = subject.diff_file(project.repository)
+
+ expect(diff_file.old_path).to eq(subject.old_path)
+ expect(diff_file.new_path).to eq(subject.new_path)
+ expect(diff_file.diff_refs).to eq(subject.diff_refs)
+ end
+ end
+
+ describe "#diff_line" do
+ it "returns the correct diff line" do
+ diff_line = subject.diff_line(project.repository)
+
+ expect(diff_line.added?).to be true
+ expect(diff_line.new_line).to eq(subject.new_line)
+ expect(diff_line.text).to eq("+ new CommitFile(@)")
+ end
+ end
+
+ describe "#line_code" do
+ it "returns the correct line code" do
+ line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.new_line, 5)
+
+ expect(subject.line_code(project.repository)).to eq(line_code)
+ end
+ end
+ end
+
+ describe "position for an unchanged line" do
+ subject do
+ described_class.new(
+ old_path: "files/js/commit.js.coffee",
+ new_path: "files/js/commit.coffee",
+ old_line: 3,
+ new_line: 3,
+ diff_refs: commit.diff_refs
+ )
+ end
+
+ describe "#diff_file" do
+ it "returns the correct diff file" do
+ diff_file = subject.diff_file(project.repository)
+
+ expect(diff_file.old_path).to eq(subject.old_path)
+ expect(diff_file.new_path).to eq(subject.new_path)
+ expect(diff_file.diff_refs).to eq(subject.diff_refs)
+ end
+ end
+
+ describe "#diff_line" do
+ it "returns the correct diff line" do
+ diff_line = subject.diff_line(project.repository)
+
+ expect(diff_line.unchanged?).to be true
+ expect(diff_line.old_line).to eq(subject.old_line)
+ expect(diff_line.new_line).to eq(subject.new_line)
+ expect(diff_line.text).to eq(" $('.files .diff-file').each ->")
+ end
+ end
+
+ describe "#line_code" do
+ it "returns the correct line code" do
+ line_code = Gitlab::Diff::LineCode.generate(subject.file_path, subject.new_line, subject.old_line)
+
+ expect(subject.line_code(project.repository)).to eq(line_code)
+ end
+ end
+ end
+
+ describe "position for a removed line" do
+ subject do
+ described_class.new(
+ old_path: "files/js/commit.js.coffee",
+ new_path: "files/js/commit.coffee",
+ old_line: 4,
+ new_line: nil,
+ diff_refs: commit.diff_refs
+ )
+ end
+
+ describe "#diff_file" do
+ it "returns the correct diff file" do
+ diff_file = subject.diff_file(project.repository)
+
+ expect(diff_file.old_path).to eq(subject.old_path)
+ expect(diff_file.new_path).to eq(subject.new_path)
+ expect(diff_file.diff_refs).to eq(subject.diff_refs)
+ end
+ end
+
+ describe "#diff_line" do
+ it "returns the correct diff line" do
+ diff_line = subject.diff_line(project.repository)
+
+ expect(diff_line.removed?).to be true
+ expect(diff_line.old_line).to eq(subject.old_line)
+ expect(diff_line.text).to eq("- new CommitFile(this)")
+ end
+ end
+
+ describe "#line_code" do
+ it "returns the correct line code" do
+ line_code = Gitlab::Diff::LineCode.generate(subject.file_path, 4, subject.old_line)
+
+ expect(subject.line_code(project.repository)).to eq(line_code)
+ end
+ end
+ end
+ end
+
+ describe "position for a deleted file" do
+ let(:commit) { project.commit("8634272bfad4cf321067c3e94d64d5a253f8321d") }
+
+ subject do
+ described_class.new(
+ old_path: "LICENSE",
+ new_path: "LICENSE",
+ old_line: 3,
+ new_line: nil,
+ diff_refs: commit.diff_refs
+ )
+ end
+
+ describe "#diff_file" do
+ it "returns the correct diff file" do
+ diff_file = subject.diff_file(project.repository)
+
+ expect(diff_file.deleted_file).to be true
+ expect(diff_file.old_path).to eq(subject.old_path)
+ expect(diff_file.diff_refs).to eq(subject.diff_refs)
+ end
+ end
+
+ describe "#diff_line" do
+ it "returns the correct diff line" do
+ diff_line = subject.diff_line(project.repository)
+
+ expect(diff_line.removed?).to be true
+ expect(diff_line.old_line).to eq(subject.old_line)
+ expect(diff_line.text).to eq("-Copyright (c) 2014 gitlabhq")
+ end
+ end
+
+ describe "#line_code" do
+ it "returns the correct line code" do
+ line_code = Gitlab::Diff::LineCode.generate(subject.file_path, 0, subject.old_line)
+
+ expect(subject.line_code(project.repository)).to eq(line_code)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/diff/position_tracer_spec.rb b/spec/lib/gitlab/diff/position_tracer_spec.rb
new file mode 100644
index 00000000000..c268f84c759
--- /dev/null
+++ b/spec/lib/gitlab/diff/position_tracer_spec.rb
@@ -0,0 +1,1759 @@
+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
+ }
+
+ merge_request = create(:merge_request, source_branch: second_create_file_commit.sha, target_branch: branch_name, source_project: project)
+ repository.merge(current_user, merge_request, 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..ae064a878b0 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -1,80 +1,55 @@
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 }
- describe 'can_push_to_branch?' do
- describe 'push to none protected branch' do
- it "returns true if user is a master" do
- project.team << [user, :master]
- expect(access.can_push_to_branch?("random_branch")).to be_truthy
- end
-
- it "returns true if user is a developer" do
- project.team << [user, :developer]
- expect(access.can_push_to_branch?("random_branch")).to be_truthy
- end
-
- it "returns false if user is a reporter" do
- project.team << [user, :reporter]
- expect(access.can_push_to_branch?("random_branch")).to be_falsey
- 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
- describe 'push to protected branch' do
+ context 'ssh disabled' do
before do
- @branch = create :protected_branch, project: project
- end
-
- it "returns true if user is a master" do
- project.team << [user, :master]
- expect(access.can_push_to_branch?(@branch.name)).to be_truthy
+ disable_protocol('ssh')
+ @acc = Gitlab::GitAccess.new(actor, project, 'ssh')
end
- it "returns false if user is a developer" do
- project.team << [user, :developer]
- expect(access.can_push_to_branch?(@branch.name)).to be_falsey
+ it 'blocks ssh git push' do
+ expect(@acc.check('git-receive-pack').allowed?).to be_falsey
end
- it "returns false if user is a reporter" do
- project.team << [user, :reporter]
- expect(access.can_push_to_branch?(@branch.name)).to be_falsey
+ it 'blocks ssh git pull' do
+ expect(@acc.check('git-upload-pack').allowed?).to be_falsey
end
end
- describe 'push to protected branch if allowed for developers' do
+ context 'http disabled' do
before do
- @branch = create :protected_branch, project: project, developers_can_push: true
- end
-
- it "returns true if user is a master" do
- project.team << [user, :master]
- expect(access.can_push_to_branch?(@branch.name)).to be_truthy
+ disable_protocol('http')
+ @acc = Gitlab::GitAccess.new(actor, project, 'http')
end
- it "returns true if user is a developer" do
- project.team << [user, :developer]
- expect(access.can_push_to_branch?(@branch.name)).to be_truthy
+ it 'blocks http push' do
+ expect(@acc.check('git-receive-pack').allowed?).to be_falsey
end
- it "returns false if user is a reporter" do
- project.team << [user, :reporter]
- expect(access.can_push_to_branch?(@branch.name)).to be_falsey
+ it 'blocks http git pull' do
+ expect(@acc.check('git-upload-pack').allowed?).to be_falsey
end
end
-
end
describe 'download_access_check' do
+ subject { access.check('git-upload-pack') }
+
describe 'master permissions' do
before { project.team << [user, :master] }
context 'pull code' do
- subject { access.download_access_check }
-
it { expect(subject.allowed?).to be_truthy }
end
end
@@ -83,8 +58,6 @@ describe Gitlab::GitAccess, lib: true do
before { project.team << [user, :guest] }
context 'pull code' do
- subject { access.download_access_check }
-
it { expect(subject.allowed?).to be_falsey }
end
end
@@ -96,16 +69,12 @@ describe Gitlab::GitAccess, lib: true do
end
context 'pull code' do
- subject { access.download_access_check }
-
it { expect(subject.allowed?).to be_falsey }
end
end
describe 'without acccess to project' do
context 'pull code' do
- subject { access.download_access_check }
-
it { expect(subject.allowed?).to be_falsey }
end
end
@@ -115,110 +84,208 @@ describe Gitlab::GitAccess, lib: true do
let(:actor) { key }
context 'pull code' do
- before { key.projects << project }
- subject { access.download_access_check }
+ context 'when project is authorized' do
+ before { key.projects << project }
- it { expect(subject.allowed?).to be_truthy }
+ it { expect(subject).to be_allowed }
+ end
+
+ context 'when unauthorized' do
+ context 'from public project' do
+ let(:project) { create(:project, :public) }
+
+ it { expect(subject).to be_allowed }
+ end
+
+ context 'from internal project' do
+ let(:project) { create(:project, :internal) }
+
+ it { expect(subject).not_to be_allowed }
+ end
+
+ context 'from private project' do
+ let(:project) { create(:project, :internal) }
+
+ it { expect(subject).not_to be_allowed }
+ end
+ end
end
end
end
describe 'push_access_check' do
- def protect_feature_branch
- create(:protected_branch, name: 'feature', project: project)
- end
+ before { merge_into_protected_branch }
+ let(:unprotected_branch) { FFaker::Internet.user_name }
- def changes
- {
- push_new_branch: "#{Gitlab::Git::BLANK_SHA} 570e7b2ab refs/heads/wow",
+ let(:changes) do
+ { push_new_branch: "#{Gitlab::Git::BLANK_SHA} 570e7b2ab refs/heads/wow",
push_master: '6f6d7e7ed 570e7b2ab refs/heads/master',
push_protected_branch: '6f6d7e7ed 570e7b2ab refs/heads/feature',
push_remove_protected_branch: "570e7b2ab #{Gitlab::Git::BLANK_SHA} "\
'refs/heads/feature',
push_tag: '6f6d7e7ed 570e7b2ab refs/tags/v1.0.0',
push_new_tag: "#{Gitlab::Git::BLANK_SHA} 570e7b2ab refs/tags/v7.8.9",
- push_all: ['6f6d7e7ed 570e7b2ab refs/heads/master', '6f6d7e7ed 570e7b2ab refs/heads/feature']
- }
+ push_all: ['6f6d7e7ed 570e7b2ab refs/heads/master', '6f6d7e7ed 570e7b2ab refs/heads/feature'],
+ merge_into_protected_branch: "0b4bc9a #{merge_into_protected_branch} refs/heads/feature" }
end
- def self.permissions_matrix
- {
- master: {
- push_new_branch: true,
- push_master: true,
- push_protected_branch: true,
- push_remove_protected_branch: false,
- push_tag: true,
- push_new_tag: true,
- push_all: true,
- },
-
- developer: {
- push_new_branch: true,
- push_master: true,
- push_protected_branch: false,
- push_remove_protected_branch: false,
- push_tag: false,
- push_new_tag: true,
- push_all: false,
- },
-
- reporter: {
- push_new_branch: false,
- push_master: false,
- push_protected_branch: false,
- push_remove_protected_branch: false,
- push_tag: false,
- push_new_tag: false,
- push_all: false,
- },
-
- guest: {
- push_new_branch: false,
- push_master: false,
- push_protected_branch: false,
- push_remove_protected_branch: false,
- push_tag: false,
- push_new_tag: false,
- push_all: false,
- }
- }
+ def stub_git_hooks
+ # Running the `pre-receive` hook is expensive, and not necessary for this test.
+ allow_any_instance_of(GitHooksService).to receive(:execute).and_yield
end
- def self.updated_permissions_matrix
- updated_permissions_matrix = permissions_matrix.dup
- updated_permissions_matrix[:developer][:push_protected_branch] = true
- updated_permissions_matrix[:developer][:push_all] = true
- updated_permissions_matrix
+ def merge_into_protected_branch
+ @protected_branch_merge_commit ||= begin
+ stub_git_hooks
+ project.repository.add_branch(user, unprotected_branch, 'feature')
+ target_branch = project.repository.lookup('feature')
+ source_branch = project.repository.commit_file(user, FFaker::InternetSE.login_user_name, FFaker::HipsterIpsum.paragraph, FFaker::HipsterIpsum.sentence, unprotected_branch, false)
+ rugged = project.repository.rugged
+ author = { email: "email@example.com", time: Time.now, name: "Example Git User" }
+
+ merge_index = rugged.merge_commits(target_branch, source_branch)
+ Rugged::Commit.create(rugged, author: author, committer: author, message: "commit message", parents: [target_branch, source_branch], tree: merge_index.write_tree(rugged))
+ end
end
- permissions_matrix.keys.each do |role|
- describe "#{role} access" do
- before { protect_feature_branch }
- before { project.team << [user, role] }
+ def self.run_permission_checks(permissions_matrix)
+ permissions_matrix.keys.each do |role|
+ describe "#{role} access" do
+ before { project.team << [user, role] }
- permissions_matrix[role].each do |action, allowed|
- context action do
- subject { access.push_access_check(changes[action]) }
+ permissions_matrix[role].each do |action, allowed|
+ context action do
+ subject { access.push_access_check(changes[action]) }
- it { expect(subject.allowed?).to allowed ? be_truthy : be_falsey }
+ it { expect(subject.allowed?).to allowed ? be_truthy : be_falsey }
+ end
end
end
end
end
- context "with enabled developers push to protected branches " do
- updated_permissions_matrix.keys.each do |role|
- describe "#{role} access" do
- before { create(:protected_branch, name: 'feature', developers_can_push: true, project: project) }
- before { project.team << [user, role] }
+ permissions_matrix = {
+ master: {
+ push_new_branch: true,
+ push_master: true,
+ push_protected_branch: true,
+ push_remove_protected_branch: false,
+ push_tag: true,
+ push_new_tag: true,
+ push_all: true,
+ merge_into_protected_branch: true
+ },
+
+ developer: {
+ push_new_branch: true,
+ push_master: true,
+ push_protected_branch: false,
+ push_remove_protected_branch: false,
+ push_tag: false,
+ push_new_tag: true,
+ push_all: false,
+ merge_into_protected_branch: false
+ },
+
+ reporter: {
+ push_new_branch: false,
+ push_master: false,
+ push_protected_branch: false,
+ push_remove_protected_branch: false,
+ push_tag: false,
+ push_new_tag: false,
+ push_all: false,
+ merge_into_protected_branch: false
+ },
+
+ guest: {
+ push_new_branch: false,
+ push_master: false,
+ push_protected_branch: false,
+ push_remove_protected_branch: false,
+ push_tag: false,
+ push_new_tag: false,
+ push_all: false,
+ merge_into_protected_branch: false
+ }
+ }
- updated_permissions_matrix[role].each do |action, allowed|
- context action do
- subject { access.push_access_check(changes[action]) }
+ [['feature', 'exact'], ['feat*', 'wildcard']].each do |protected_branch_name, protected_branch_type|
+ context do
+ before { create(:protected_branch, name: protected_branch_name, project: project) }
- it { expect(subject.allowed?).to allowed ? be_truthy : be_falsey }
+ run_permission_checks(permissions_matrix)
+ end
+
+ context "when 'developers can push' is turned on for the #{protected_branch_type} protected branch" do
+ before { create(:protected_branch, name: protected_branch_name, developers_can_push: true, project: project) }
+
+ run_permission_checks(permissions_matrix.deep_merge(developer: { push_protected_branch: true, push_all: true, merge_into_protected_branch: true }))
+ end
+
+ context "when 'developers can merge' is turned on for the #{protected_branch_type} protected branch" do
+ before { create(:protected_branch, name: protected_branch_name, developers_can_merge: true, project: project) }
+
+ context "when a merge request exists for the given source/target branch" do
+ context "when the merge request is in progress" do
+ before do
+ create(:merge_request, source_project: project, source_branch: unprotected_branch, target_branch: 'feature', state: 'locked', in_progress_merge_commit_sha: merge_into_protected_branch)
end
+
+ run_permission_checks(permissions_matrix.deep_merge(developer: { merge_into_protected_branch: true }))
+ end
+
+ context "when the merge request is not in progress" do
+ before do
+ create(:merge_request, source_project: project, source_branch: unprotected_branch, target_branch: 'feature', in_progress_merge_commit_sha: nil)
+ end
+
+ run_permission_checks(permissions_matrix.deep_merge(developer: { merge_into_protected_branch: false }))
+ end
+ end
+
+ context "when a merge request does not exist for the given source/target branch" do
+ run_permission_checks(permissions_matrix.deep_merge(developer: { merge_into_protected_branch: false }))
+ end
+ end
+
+ context "when 'developers can merge' and 'developers can push' are turned on for the #{protected_branch_type} protected branch" do
+ before { create(:protected_branch, name: protected_branch_name, developers_can_merge: true, developers_can_push: true, project: project) }
+
+ run_permission_checks(permissions_matrix.deep_merge(developer: { push_protected_branch: true, push_all: true, merge_into_protected_branch: true }))
+ end
+ end
+
+ describe 'deploy key permissions' do
+ let(:key) { create(:deploy_key) }
+ let(:actor) { key }
+
+ context 'push code' do
+ subject { access.check('git-receive-pack') }
+
+ context 'when project is authorized' do
+ before { key.projects << project }
+
+ it { expect(subject).not_to be_allowed }
+ end
+
+ context 'when unauthorized' do
+ context 'to public project' do
+ let(:project) { create(:project, :public) }
+
+ it { expect(subject).not_to be_allowed }
+ end
+
+ context 'to internal project' do
+ let(:project) { create(:project, :internal) }
+
+ it { expect(subject).not_to be_allowed }
+ end
+
+ context 'to private project' do
+ let(:project) { create(:project, :internal) }
+
+ it { expect(subject).not_to be_allowed }
end
end
end
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..613c47d55f1 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
@@ -47,4 +61,11 @@ describe Gitlab::GithubImport::Client, lib: true do
expect(client.api.api_endpoint).to eq 'https://github.company.com/'
end
end
+
+ it 'does not raise error when rate limit is disabled' do
+ stub_request(:get, /api.github.com/)
+ allow(client.api).to receive(:rate_limit!).and_raise(Octokit::NotFound)
+
+ expect { client.issues }.not_to raise_error
+ end
end
diff --git a/spec/lib/gitlab/github_import/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/gitlab_import/importer_spec.rb b/spec/lib/gitlab/gitlab_import/importer_spec.rb
new file mode 100644
index 00000000000..d3f1deb3837
--- /dev/null
+++ b/spec/lib/gitlab/gitlab_import/importer_spec.rb
@@ -0,0 +1,53 @@
+require 'spec_helper'
+
+describe Gitlab::GitlabImport::Importer, lib: true do
+ include ImportSpecHelper
+
+ describe '#execute' do
+ before do
+ stub_omniauth_provider('gitlab')
+ stub_request('issues', [
+ {
+ 'id' => 2579857,
+ 'iid' => 3,
+ 'title' => 'Issue',
+ 'description' => 'Lorem ipsum',
+ 'state' => 'opened',
+ 'author' => {
+ 'id' => 283999,
+ 'name' => 'John Doe'
+ }
+ }
+ ])
+ stub_request('issues/2579857/notes', [])
+ end
+
+ it 'persists issues' do
+ project = create(:empty_project, import_source: 'asd/vim')
+ project.build_import_data(credentials: { password: 'password' })
+
+ subject = described_class.new(project)
+ subject.execute
+
+ expected_attributes = {
+ iid: 3,
+ title: 'Issue',
+ description: "*Created by: John Doe*\n\nLorem ipsum",
+ state: 'opened',
+ author_id: project.creator_id
+ }
+
+ expect(project.issues.first).to have_attributes(expected_attributes)
+ end
+
+ def stub_request(path, body)
+ url = "https://gitlab.com/api/v3/projects/asd%2Fvim/#{path}?page=1&per_page=100"
+
+ WebMock.stub_request(:get, url).
+ to_return(
+ headers: { 'Content-Type' => 'application/json' },
+ body: body
+ )
+ end
+ end
+end
diff --git a/spec/lib/gitlab/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/avatar_restorer_spec.rb b/spec/lib/gitlab/import_export/avatar_restorer_spec.rb
new file mode 100644
index 00000000000..5ae178414cc
--- /dev/null
+++ b/spec/lib/gitlab/import_export/avatar_restorer_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe Gitlab::ImportExport::AvatarRestorer, lib: true do
+ let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: 'test') }
+ let(:project) { create(:empty_project) }
+
+ before do
+ allow_any_instance_of(described_class).to receive(:avatar_export_file)
+ .and_return(Rails.root + "spec/fixtures/dk.png")
+ end
+
+ after do
+ project.remove_avatar!
+ end
+
+ it 'restores a project avatar' do
+ expect(described_class.new(project: project, shared: shared).restore).to be true
+ end
+
+ it 'saves the avatar into the project' do
+ described_class.new(project: project, shared: shared).restore
+
+ expect(project.reload.avatar.file.exists?).to be true
+ end
+end
diff --git a/spec/lib/gitlab/import_export/avatar_saver_spec.rb b/spec/lib/gitlab/import_export/avatar_saver_spec.rb
new file mode 100644
index 00000000000..d6ee94442cb
--- /dev/null
+++ b/spec/lib/gitlab/import_export/avatar_saver_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe Gitlab::ImportExport::AvatarSaver, lib: true do
+ let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: 'test') }
+ let(:export_path) { "#{Dir::tmpdir}/project_tree_saver_spec" }
+ let(:project_with_avatar) { create(:empty_project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
+ let(:project) { create(:empty_project) }
+
+ before do
+ FileUtils.mkdir_p("#{shared.export_path}/avatar/")
+ allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
+ end
+
+ after do
+ FileUtils.rm_rf("#{shared.export_path}/avatar")
+ end
+
+ it 'saves a project avatar' do
+ described_class.new(project: project_with_avatar, shared: shared).save
+
+ expect(File).to exist("#{shared.export_path}/avatar/dk.png")
+ end
+
+ it 'is fine not to have an avatar' do
+ expect(described_class.new(project: project, shared: shared).save).to be true
+ end
+end
diff --git a/spec/lib/gitlab/import_export/import_export_spec.rb b/spec/lib/gitlab/import_export/import_export_spec.rb
new file mode 100644
index 00000000000..d6409a29550
--- /dev/null
+++ b/spec/lib/gitlab/import_export/import_export_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe Gitlab::ImportExport, services: true do
+ describe 'export filename' do
+ let(:project) { create(:project, :public, path: 'project-path') }
+
+ it 'contains the project path' do
+ expect(described_class.export_filename(project: project)).to include(project.path)
+ end
+
+ it 'contains the namespace path' do
+ expect(described_class.export_filename(project: project)).to include(project.namespace.path)
+ end
+
+ it 'does not go over a certain length' do
+ project.path = 'a' * 100
+
+ expect(described_class.export_filename(project: project).length).to be < 70
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/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 400d44ac162..b1a5d72c624 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -1,62 +1,64 @@
{
- "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,
+ "test_ee_field": "test",
"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 +70,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 +94,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 +118,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 +141,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 +165,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 +189,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 +213,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 +259,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 +283,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 +307,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 +331,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 +355,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 +379,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 +403,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 +427,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 +473,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 +497,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 +521,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 +545,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 +569,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 +593,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 +617,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 +641,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 +687,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 +711,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 +735,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 +759,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 +783,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 +807,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 +831,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 +855,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 +901,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 +925,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 +949,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 +973,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 +997,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 +1021,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 +1045,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 +1069,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 +1115,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 +1139,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 +1163,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 +1187,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 +1211,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 +1235,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 +1259,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 +1283,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 +1329,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 +1353,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 +1377,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 +1401,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 +1425,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 +1449,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 +1473,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 +1497,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 +1543,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 +1567,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 +1591,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 +1615,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 +1639,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 +1663,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 +1687,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 +1711,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 +1757,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 +1781,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 +1805,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 +1829,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 +1853,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 +1877,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 +1901,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 +1925,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 +1971,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 +1995,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 +2019,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 +2043,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 +2067,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 +2091,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 +2115,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 +2139,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 +2482,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 +2490,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": [
{
@@ -2658,7 +2765,7 @@
"committer_email": "dmitriy.zaporozhets@gmail.com"
}
],
- "st_diffs": [
+ "utf8_st_diffs": [
{
"diff": "Binary files a/.DS_Store and /dev/null differ\n",
"new_path": ".DS_Store",
@@ -2759,23 +2866,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 +2920,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 +2928,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": [
{
@@ -2981,7 +3138,7 @@
"committer_email": "dmitriy.zaporozhets@gmail.com"
}
],
- "st_diffs": [
+ "utf8_st_diffs": [
{
"diff": "--- /dev/null\n+++ b/files/ruby/feature.rb\n@@ -0,0 +1,5 @@\n+class Feature\n+ def foo\n+ puts 'bar'\n+ end\n+end\n",
"new_path": "files/ruby/feature.rb",
@@ -2994,35 +3151,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 +3213,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 +3229,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 +3253,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 +3277,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 +3301,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 +3325,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 +3349,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 +3373,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 +3397,12 @@
"system": false,
"st_diff": null,
"updated_by_id": null,
- "author": {
- "name": "User 4"
- }
+ "author": {
+ "name": "Administrator"
+ },
+ "events": [
+
+ ]
}
],
"merge_request_diff": {
@@ -3203,6 +3410,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"
+ }
+ ],
+ "utf8_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,9 +3893,107 @@
"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": [
+ "utf8_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",
@@ -3414,6 +4017,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 +4050,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 +4092,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 +4128,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 +4189,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 +4205,31 @@
"system": false,
"st_diff": null,
"updated_by_id": null,
- "author": {
- "name": "Administrator"
- }
- },
- {
- "id": 1246,
- "note": "Eos optio et architecto eligendi ea est nihil.",
+ "author": {
+ "name": "User 4"
+ },
+ "events": [
+ {
+ "id": 529,
+ "target_type": "Note",
+ "target_id": 2521,
+ "title": "test levels",
+ "data": null,
+ "project_id": 4,
+ "created_at": "2016-07-07T14:35:12.128Z",
+ "updated_at": "2016-07-07T14:35:12.128Z",
+ "action": 6,
+ "author_id": 1
+ }
+ ]
+ },
+ {
+ "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 +4240,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 +4264,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 +4288,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 +4312,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 +4336,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 +4360,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 +4384,12 @@
"system": false,
"st_diff": null,
"updated_by_id": null,
- "author": {
- "name": "User 4"
- }
+ "author": {
+ "name": "Administrator"
+ },
+ "events": [
+
+ ]
}
],
"merge_request_diff": {
@@ -3902,20 +4397,273 @@
"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": [
+ "utf8_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",
@@ -3929,34 +4677,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 +4738,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 +4754,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 +4778,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 +4802,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 +4826,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 +4850,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 +4874,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 +4898,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 +4922,12 @@
"system": false,
"st_diff": null,
"updated_by_id": null,
- "author": {
- "name": "User 4"
- }
+ "author": {
+ "name": "Administrator"
+ },
+ "events": [
+
+ ]
}
],
"merge_request_diff": {
@@ -4143,14 +4941,229 @@
"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": [
+ "utf8_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",
@@ -4164,34 +5177,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 +5238,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 +5254,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 +5278,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 +5302,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 +5326,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 +5350,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 +5374,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 +5398,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 +5422,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"
- }
- ],
- "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
- }
+ "state": "empty",
+ "st_commits": null,
+ "utf8_st_diffs": [
+
],
"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 +5499,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 +5515,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 +5539,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 +5563,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 +5587,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 +5611,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 +5635,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 +5659,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 +5683,12 @@
"system": false,
"st_diff": null,
"updated_by_id": null,
- "author": {
- "name": "User 4"
- }
+ "author": {
+ "name": "Administrator"
+ },
+ "events": [
+
+ ]
}
],
"merge_request_diff": {
@@ -4607,20 +5696,426 @@
"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": [
+ "utf8_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",
@@ -4634,34 +6129,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 +6190,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 +6206,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 +6230,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 +6254,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 +6278,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 +6302,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 +6326,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 +6350,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 +6374,12 @@
"system": false,
"st_diff": null,
"updated_by_id": null,
- "author": {
- "name": "User 4"
- }
+ "author": {
+ "name": "Administrator"
+ },
+ "events": [
+
+ ]
}
],
"merge_request_diff": {
@@ -4842,20 +6387,20 @@
"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": [
+ "utf8_st_diffs": [
{
"diff": "--- /dev/null\n+++ b/test\n",
"new_path": "test",
@@ -4869,11 +6414,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": [
@@ -4894,6 +6465,29 @@
"started_at": null,
"finished_at": null,
"duration": null,
+ "notes": [
+ {
+ "id": 999,
+ "note": "Natus rerum qui dolorem dolorum voluptas.",
+ "noteable_type": "Commit",
+ "author_id": 1,
+ "created_at": "2016-03-22T15:19:59.469Z",
+ "updated_at": "2016-03-22T15:19:59.469Z",
+ "project_id": 5,
+ "attachment": {
+ "url": null
+ },
+ "line_code": null,
+ "commit_id": "be93687618e4b132087f430a4d8fc3a609c9b77c",
+ "noteable_id": 36,
+ "system": false,
+ "st_diff": null,
+ "updated_by_id": null,
+ "author": {
+ "name": "Administrator"
+ }
+ }
+ ],
"statuses": [
{
"id": 71,
@@ -5337,5 +6931,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 7a40a43f8ae..6ae20c943b1 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -18,6 +18,48 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
it 'restores models based on JSON' do
expect(restored_project_json).to be true
end
+
+ it 'creates a valid pipeline note' do
+ restored_project_json
+
+ expect(Ci::Pipeline.first.notes).not_to be_empty
+ end
+
+ it 'restores the correct event with symbolised data' do
+ restored_project_json
+
+ expect(Event.where.not(data: nil).first.data[:ref]).not_to be_empty
+ end
+
+ it 'preserves updated_at on issues' do
+ restored_project_json
+
+ issue = Issue.where(description: 'Aliquam enim illo et possimus.').first
+
+ expect(issue.reload.updated_at.to_s).to eq('2016-06-14 15:02:47 UTC')
+ end
+
+ context 'event at forth level of the tree' do
+ let(:event) { Event.where(title: 'test levels').first }
+
+ before do
+ restored_project_json
+ end
+
+ it 'restores the event' do
+ expect(event).not_to be_nil
+ end
+
+ it 'event belongs to note, belongs to merge request, belongs to a project' do
+ expect(event.note.noteable.project).not_to be_nil
+ end
+ end
+
+ it 'has the correct data for merge request st_diffs' do
+ # makes sure we are renaming the custom method +utf8_st_diffs+ into +st_diffs+
+
+ expect { restored_project_json }.to change(MergeRequestDiff.where.not(st_diffs: nil), :count).by(9)
+ 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..057ef6e76a0 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
@@ -104,12 +102,17 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
it 'has ci pipeline notes' do
expect(saved_project_json['pipelines'].first['notes']).not_to be_empty
end
+
+ it 'does not complain about non UTF-8 characters in MR diffs' do
+ ActiveRecord::Base.connection.execute("UPDATE merge_request_diffs SET st_diffs = '---\n- :diff: !binary |-\n LS0tIC9kZXYvbnVsbAorKysgYi9pbWFnZXMvbnVjb3IucGRmCkBAIC0wLDAg\n KzEsMTY3OSBAQAorJVBERi0xLjUNJeLjz9MNCisxIDAgb2JqDTw8L01ldGFk\n YXR'")
+
+ expect(project_tree_saver.save).to be true
+ end
end
end
def setup_project
issue = create(:issue, assignee: user)
- merge_request = create(:merge_request)
label = create(:label)
snippet = create(:project_snippet)
release = create(:release)
@@ -117,22 +120,22 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
project = create(:project,
:public,
issues: [issue],
- merge_requests: [merge_request],
labels: [label],
snippets: [snippet],
releases: [release]
)
+ merge_request = create(:merge_request, source_project: project)
commit_status = create(:commit_status, project: project)
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 +143,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/access_spec.rb b/spec/lib/gitlab/ldap/access_spec.rb
index f5b66b8156f..acd5394382c 100644
--- a/spec/lib/gitlab/ldap/access_spec.rb
+++ b/spec/lib/gitlab/ldap/access_spec.rb
@@ -4,7 +4,7 @@ describe Gitlab::LDAP::Access, lib: true do
let(:access) { Gitlab::LDAP::Access.new user }
let(:user) { create(:omniauth_user) }
- describe :allowed? do
+ describe '#allowed?' do
subject { access.allowed? }
context 'when the user cannot be found' do
diff --git a/spec/lib/gitlab/ldap/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/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb
index 03199a2523e..949f6e2b19a 100644
--- a/spec/lib/gitlab/ldap/user_spec.rb
+++ b/spec/lib/gitlab/ldap/user_spec.rb
@@ -25,7 +25,7 @@ describe Gitlab::LDAP::User, lib: true do
OmniAuth::AuthHash.new(uid: 'my-uid', provider: 'ldapmain', info: info_upper_case)
end
- describe :changed? do
+ describe '#changed?' do
it "marks existing ldap user as changed" do
create(:omniauth_user, extern_uid: 'my-uid', provider: 'ldapmain')
expect(ldap_user.changed?).to be_truthy
diff --git a/spec/lib/gitlab/lfs/lfs_router_spec.rb b/spec/lib/gitlab/lfs/lfs_router_spec.rb
deleted file mode 100644
index 88814bc474d..00000000000
--- a/spec/lib/gitlab/lfs/lfs_router_spec.rb
+++ /dev/null
@@ -1,765 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Lfs::Router, lib: true do
- let(:project) { create(:project) }
- let(:public_project) { create(:project, :public) }
- let(:forked_project) { fork_project(public_project, user) }
-
- let(:user) { create(:user) }
- let(:user_two) { create(:user) }
- let!(:lfs_object) { create(:lfs_object, :with_file) }
-
- let(:request) { Rack::Request.new(env) }
- let(:env) do
- {
- 'rack.input' => '',
- 'REQUEST_METHOD' => 'GET',
- }
- end
-
- let(:lfs_router_auth) { new_lfs_router(project, user) }
- let(:lfs_router_noauth) { new_lfs_router(project, nil) }
- let(:lfs_router_public_auth) { new_lfs_router(public_project, user) }
- let(:lfs_router_public_noauth) { new_lfs_router(public_project, nil) }
- let(:lfs_router_forked_noauth) { new_lfs_router(forked_project, nil) }
- let(:lfs_router_forked_auth) { new_lfs_router(forked_project, user_two) }
-
- let(:sample_oid) { "b68143e6463773b1b6c6fd009a76c32aeec041faff32ba2ed42fd7f708a17f80" }
- let(:sample_size) { 499013 }
- let(:respond_with_deprecated) {[ 501, { "Content-Type" => "application/json; charset=utf-8" }, ["{\"message\":\"Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.\",\"documentation_url\":\"#{Gitlab.config.gitlab.url}/help\"}"]]}
- let(:respond_with_disabled) {[ 501, { "Content-Type" => "application/json; charset=utf-8" }, ["{\"message\":\"Git LFS is not enabled on this GitLab server, contact your admin.\",\"documentation_url\":\"#{Gitlab.config.gitlab.url}/help\"}"]]}
-
- describe 'when lfs is disabled' do
- before do
- allow(Gitlab.config.lfs).to receive(:enabled).and_return(false)
- env['REQUEST_METHOD'] = 'POST'
- body = {
- 'objects' => [
- { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
- 'size' => 1575078
- },
- { 'oid' => sample_oid,
- 'size' => sample_size
- }
- ],
- 'operation' => 'upload'
- }.to_json
- env['rack.input'] = StringIO.new(body)
- env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/batch"
- end
-
- it 'responds with 501' do
- expect(lfs_router_auth.try_call).to match_array(respond_with_disabled)
- end
- end
-
- describe 'when fetching lfs object using deprecated API' do
- before do
- enable_lfs
- env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/#{sample_oid}"
- end
-
- it 'responds with 501' do
- expect(lfs_router_auth.try_call).to match_array(respond_with_deprecated)
- end
- end
-
- describe 'when fetching lfs object' do
- before do
- enable_lfs
- env['HTTP_ACCEPT'] = "application/vnd.git-lfs+json; charset=utf-8"
- env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}"
- end
-
- describe 'and request comes from gitlab-workhorse' do
- context 'without user being authorized' do
- it "responds with status 401" do
- expect(lfs_router_noauth.try_call.first).to eq(401)
- end
- end
-
- context 'with required headers' do
- before do
- env['HTTP_X_SENDFILE_TYPE'] = "X-Sendfile"
- end
-
- context 'when user does not have project access' do
- it "responds with status 403" do
- expect(lfs_router_auth.try_call.first).to eq(403)
- end
- end
-
- context 'when user has project access' do
- before do
- project.lfs_objects << lfs_object
- project.team << [user, :master]
- end
-
- it "responds with status 200" do
- expect(lfs_router_auth.try_call.first).to eq(200)
- end
-
- it "responds with the file location" do
- expect(lfs_router_auth.try_call[1]['Content-Type']).to eq("application/octet-stream")
- expect(lfs_router_auth.try_call[1]['X-Sendfile']).to eq(lfs_object.file.path)
- end
- end
- end
-
- context 'without required headers' do
- it "responds with status 403" do
- expect(lfs_router_auth.try_call.first).to eq(403)
- end
- end
- end
- end
-
- describe 'when handling lfs request using deprecated API' do
- before do
- enable_lfs
- env['REQUEST_METHOD'] = 'POST'
- env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects"
- end
-
- it 'responds with 501' do
- expect(lfs_router_auth.try_call).to match_array(respond_with_deprecated)
- end
- end
-
- describe 'when handling lfs batch request' do
- before do
- enable_lfs
- env['REQUEST_METHOD'] = 'POST'
- env['PATH_INFO'] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/batch"
- end
-
- describe 'download' do
- describe 'when user is authenticated' do
- before do
- body = { 'operation' => 'download',
- 'objects' => [
- { 'oid' => sample_oid,
- 'size' => sample_size
- }]
- }.to_json
- env['rack.input'] = StringIO.new(body)
- end
-
- describe 'when user has download access' do
- before do
- @auth = authorize(user)
- env["HTTP_AUTHORIZATION"] = @auth
- project.team << [user, :reporter]
- end
-
- context 'when downloading an lfs object that is assigned to our project' do
- before do
- project.lfs_objects << lfs_object
- end
-
- it 'responds with status 200 and href to download' do
- response = lfs_router_auth.try_call
- expect(response.first).to eq(200)
- response_body = ActiveSupport::JSON.decode(response.last.first)
-
- expect(response_body).to eq('objects' => [
- { 'oid' => sample_oid,
- 'size' => sample_size,
- 'actions' => {
- 'download' => {
- 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
- 'header' => { 'Authorization' => @auth }
- }
- }
- }])
- end
- end
-
- context 'when downloading an lfs object that is assigned to other project' do
- before do
- public_project.lfs_objects << lfs_object
- end
-
- it 'responds with status 200 and error message' do
- response = lfs_router_auth.try_call
- expect(response.first).to eq(200)
- response_body = ActiveSupport::JSON.decode(response.last.first)
-
- expect(response_body).to eq('objects' => [
- { 'oid' => sample_oid,
- 'size' => sample_size,
- 'error' => {
- 'code' => 404,
- 'message' => "Object does not exist on the server or you don't have permissions to access it",
- }
- }])
- end
- end
-
- context 'when downloading a lfs object that does not exist' do
- before do
- body = { 'operation' => 'download',
- 'objects' => [
- { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
- 'size' => 1575078
- }]
- }.to_json
- env['rack.input'] = StringIO.new(body)
- end
-
- it "responds with status 200 and error message" do
- response = lfs_router_auth.try_call
- expect(response.first).to eq(200)
- response_body = ActiveSupport::JSON.decode(response.last.first)
-
- expect(response_body).to eq('objects' => [
- { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
- 'size' => 1575078,
- 'error' => {
- 'code' => 404,
- 'message' => "Object does not exist on the server or you don't have permissions to access it",
- }
- }])
- end
- end
-
- context 'when downloading one new and one existing lfs object' do
- before do
- body = { 'operation' => 'download',
- 'objects' => [
- { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
- 'size' => 1575078
- },
- { 'oid' => sample_oid,
- 'size' => sample_size
- }
- ]
- }.to_json
- env['rack.input'] = StringIO.new(body)
- project.lfs_objects << lfs_object
- end
-
- it "responds with status 200 with upload hypermedia link for the new object" do
- response = lfs_router_auth.try_call
- expect(response.first).to eq(200)
- response_body = ActiveSupport::JSON.decode(response.last.first)
-
- expect(response_body).to eq('objects' => [
- { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
- 'size' => 1575078,
- 'error' => {
- 'code' => 404,
- 'message' => "Object does not exist on the server or you don't have permissions to access it",
- }
- },
- { 'oid' => sample_oid,
- 'size' => sample_size,
- 'actions' => {
- 'download' => {
- 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
- 'header' => { 'Authorization' => @auth }
- }
- }
- }])
- end
- end
- end
-
- context 'when user does is not member of the project' do
- before do
- @auth = authorize(user)
- env["HTTP_AUTHORIZATION"] = @auth
- project.team << [user, :guest]
- end
-
- it 'responds with 403' do
- expect(lfs_router_auth.try_call.first).to eq(403)
- end
- end
-
- context 'when user does not have download access' do
- before do
- @auth = authorize(user)
- env["HTTP_AUTHORIZATION"] = @auth
- project.team << [user, :guest]
- end
-
- it 'responds with 403' do
- expect(lfs_router_auth.try_call.first).to eq(403)
- end
- end
- end
-
- context 'when user is not authenticated' do
- before do
- body = { 'operation' => 'download',
- 'objects' => [
- { 'oid' => sample_oid,
- 'size' => sample_size
- }],
-
- }.to_json
- env['rack.input'] = StringIO.new(body)
- end
-
- describe 'is accessing public project' do
- before do
- public_project.lfs_objects << lfs_object
- end
-
- it 'responds with status 200 and href to download' do
- response = lfs_router_public_noauth.try_call
- expect(response.first).to eq(200)
- response_body = ActiveSupport::JSON.decode(response.last.first)
-
- expect(response_body).to eq('objects' => [
- { 'oid' => sample_oid,
- 'size' => sample_size,
- 'actions' => {
- 'download' => {
- 'href' => "#{public_project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
- 'header' => {}
- }
- }
- }])
- end
- end
-
- describe 'is accessing non-public project' do
- before do
- project.lfs_objects << lfs_object
- end
-
- it 'responds with authorization required' do
- expect(lfs_router_noauth.try_call.first).to eq(401)
- end
- end
- end
- end
-
- describe 'upload' do
- describe 'when user is authenticated' do
- before do
- body = { 'operation' => 'upload',
- 'objects' => [
- { 'oid' => sample_oid,
- 'size' => sample_size
- }]
- }.to_json
- env['rack.input'] = StringIO.new(body)
- end
-
- describe 'when user has project push access' do
- before do
- @auth = authorize(user)
- env["HTTP_AUTHORIZATION"] = @auth
- project.team << [user, :developer]
- end
-
- context 'when pushing an lfs object that already exists' do
- before do
- public_project.lfs_objects << lfs_object
- end
-
- it "responds with status 200 and links the object to the project" do
- response_body = lfs_router_auth.try_call.last
- response = ActiveSupport::JSON.decode(response_body.first)
-
- expect(response['objects']).to be_kind_of(Array)
- expect(response['objects'].first['oid']).to eq(sample_oid)
- expect(response['objects'].first['size']).to eq(sample_size)
- expect(lfs_object.projects.pluck(:id)).not_to include(project.id)
- expect(lfs_object.projects.pluck(:id)).to include(public_project.id)
- expect(response['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}")
- expect(response['objects'].first['actions']['upload']['header']).to eq('Authorization' => @auth)
- end
- end
-
- context 'when pushing a lfs object that does not exist' do
- before do
- body = { 'operation' => 'upload',
- 'objects' => [
- { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
- 'size' => 1575078
- }]
- }.to_json
- env['rack.input'] = StringIO.new(body)
- end
-
- it "responds with status 200 and upload hypermedia link" do
- response = lfs_router_auth.try_call
- expect(response.first).to eq(200)
-
- response_body = ActiveSupport::JSON.decode(response.last.first)
- expect(response_body['objects']).to be_kind_of(Array)
- expect(response_body['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897")
- expect(response_body['objects'].first['size']).to eq(1575078)
- expect(lfs_object.projects.pluck(:id)).not_to include(project.id)
- expect(response_body['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078")
- expect(response_body['objects'].first['actions']['upload']['header']).to eq('Authorization' => @auth)
- end
- end
-
- context 'when pushing one new and one existing lfs object' do
- before do
- body = { 'operation' => 'upload',
- 'objects' => [
- { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
- 'size' => 1575078
- },
- { 'oid' => sample_oid,
- 'size' => sample_size
- }
- ]
- }.to_json
- env['rack.input'] = StringIO.new(body)
- project.lfs_objects << lfs_object
- end
-
- it "responds with status 200 with upload hypermedia link for the new object" do
- response = lfs_router_auth.try_call
- expect(response.first).to eq(200)
-
- response_body = ActiveSupport::JSON.decode(response.last.first)
- expect(response_body['objects']).to be_kind_of(Array)
-
- expect(response_body['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897")
- expect(response_body['objects'].first['size']).to eq(1575078)
- expect(response_body['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078")
- expect(response_body['objects'].first['actions']['upload']['header']).to eq("Authorization" => @auth)
-
- expect(response_body['objects'].last['oid']).to eq(sample_oid)
- expect(response_body['objects'].last['size']).to eq(sample_size)
- expect(response_body['objects'].last).not_to have_key('actions')
- end
- end
- end
-
- context 'when user does not have push access' do
- it 'responds with 403' do
- expect(lfs_router_auth.try_call.first).to eq(403)
- end
- end
- end
-
- context 'when user is not authenticated' do
- before do
- env['rack.input'] = StringIO.new(
- { 'objects' => [], 'operation' => 'upload' }.to_json
- )
- end
-
- context 'when user has push access' do
- before do
- project.team << [user, :master]
- end
-
- it "responds with status 401" do
- expect(lfs_router_public_noauth.try_call.first).to eq(401)
- end
- end
-
- context 'when user does not have push access' do
- it "responds with status 401" do
- expect(lfs_router_public_noauth.try_call.first).to eq(401)
- end
- end
- end
- end
-
- describe 'unsupported' do
- before do
- body = { 'operation' => 'other',
- 'objects' => [
- { 'oid' => sample_oid,
- 'size' => sample_size
- }]
- }.to_json
- env['rack.input'] = StringIO.new(body)
- end
-
- it 'responds with status 404' do
- expect(lfs_router_public_noauth.try_call.first).to eq(404)
- end
- end
- end
-
- describe 'when pushing a lfs object' do
- before do
- enable_lfs
- env['REQUEST_METHOD'] = 'PUT'
- end
-
- describe 'to one project' do
- describe 'when user has push access to the project' do
- before do
- project.team << [user, :master]
- end
-
- describe 'when user is authenticated' do
- context 'and request is sent by gitlab-workhorse to authorize the request' do
- before do
- header_for_upload_authorize(project)
- end
-
- it 'responds with status 200, location of lfs store and object details' do
- json_response = ActiveSupport::JSON.decode(lfs_router_auth.try_call.last.first)
-
- expect(lfs_router_auth.try_call.first).to eq(200)
- expect(json_response['StoreLFSPath']).to eq("#{Gitlab.config.shared.path}/lfs-objects/tmp/upload")
- expect(json_response['LfsOid']).to eq(sample_oid)
- expect(json_response['LfsSize']).to eq(sample_size)
- end
- end
-
- context 'and request is sent by gitlab-workhorse to finalize the upload' do
- before do
- headers_for_upload_finalize(project)
- end
-
- it 'responds with status 200 and lfs object is linked to the project' do
- expect(lfs_router_auth.try_call.first).to eq(200)
- expect(lfs_object.projects.pluck(:id)).to include(project.id)
- end
- end
- end
-
- describe 'when user is unauthenticated' do
- let(:lfs_router_noauth) { new_lfs_router(project, nil) }
-
- context 'and request is sent by gitlab-workhorse to authorize the request' do
- before do
- header_for_upload_authorize(project)
- end
-
- it 'responds with status 401' do
- expect(lfs_router_noauth.try_call.first).to eq(401)
- end
- end
-
- context 'and request is sent by gitlab-workhorse to finalize the upload' do
- before do
- headers_for_upload_finalize(project)
- end
-
- it 'responds with status 401' do
- expect(lfs_router_noauth.try_call.first).to eq(401)
- end
- end
-
- context 'and request is sent with a malformed headers' do
- before do
- env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}"
- env["HTTP_X_GITLAB_LFS_TMP"] = "cat /etc/passwd"
- end
-
- it 'does not recognize it as a valid lfs command' do
- expect(lfs_router_noauth.try_call).to eq(nil)
- end
- end
- end
- end
-
- describe 'and user does not have push access' do
- describe 'when user is authenticated' do
- context 'and request is sent by gitlab-workhorse to authorize the request' do
- before do
- header_for_upload_authorize(project)
- end
-
- it 'responds with 403' do
- expect(lfs_router_auth.try_call.first).to eq(403)
- end
- end
-
- context 'and request is sent by gitlab-workhorse to finalize the upload' do
- before do
- headers_for_upload_finalize(project)
- end
-
- it 'responds with 403' do
- expect(lfs_router_auth.try_call.first).to eq(403)
- end
- end
- end
-
- describe 'when user is unauthenticated' do
- let(:lfs_router_noauth) { new_lfs_router(project, nil) }
-
- context 'and request is sent by gitlab-workhorse to authorize the request' do
- before do
- header_for_upload_authorize(project)
- end
-
- it 'responds with 401' do
- expect(lfs_router_noauth.try_call.first).to eq(401)
- end
- end
-
- context 'and request is sent by gitlab-workhorse to finalize the upload' do
- before do
- headers_for_upload_finalize(project)
- end
-
- it 'responds with 401' do
- expect(lfs_router_noauth.try_call.first).to eq(401)
- end
- end
- end
- end
- end
-
- describe "to a forked project" do
- let(:forked_project) { fork_project(public_project, user) }
-
- describe 'when user has push access to the project' do
- before do
- forked_project.team << [user_two, :master]
- end
-
- describe 'when user is authenticated' do
- context 'and request is sent by gitlab-workhorse to authorize the request' do
- before do
- header_for_upload_authorize(forked_project)
- end
-
- it 'responds with status 200, location of lfs store and object details' do
- json_response = ActiveSupport::JSON.decode(lfs_router_forked_auth.try_call.last.first)
-
- expect(lfs_router_forked_auth.try_call.first).to eq(200)
- expect(json_response['StoreLFSPath']).to eq("#{Gitlab.config.shared.path}/lfs-objects/tmp/upload")
- expect(json_response['LfsOid']).to eq(sample_oid)
- expect(json_response['LfsSize']).to eq(sample_size)
- end
- end
-
- context 'and request is sent by gitlab-workhorse to finalize the upload' do
- before do
- headers_for_upload_finalize(forked_project)
- end
-
- it 'responds with status 200 and lfs object is linked to the source project' do
- expect(lfs_router_forked_auth.try_call.first).to eq(200)
- expect(lfs_object.projects.pluck(:id)).to include(public_project.id)
- end
- end
- end
-
- describe 'when user is unauthenticated' do
- context 'and request is sent by gitlab-workhorse to authorize the request' do
- before do
- header_for_upload_authorize(forked_project)
- end
-
- it 'responds with status 401' do
- expect(lfs_router_forked_noauth.try_call.first).to eq(401)
- end
- end
-
- context 'and request is sent by gitlab-workhorse to finalize the upload' do
- before do
- headers_for_upload_finalize(forked_project)
- end
-
- it 'responds with status 401' do
- expect(lfs_router_forked_noauth.try_call.first).to eq(401)
- end
- end
- end
- end
-
- describe 'and user does not have push access' do
- describe 'when user is authenticated' do
- context 'and request is sent by gitlab-workhorse to authorize the request' do
- before do
- header_for_upload_authorize(forked_project)
- end
-
- it 'responds with 403' do
- expect(lfs_router_forked_auth.try_call.first).to eq(403)
- end
- end
-
- context 'and request is sent by gitlab-workhorse to finalize the upload' do
- before do
- headers_for_upload_finalize(forked_project)
- end
-
- it 'responds with 403' do
- expect(lfs_router_forked_auth.try_call.first).to eq(403)
- end
- end
- end
-
- describe 'when user is unauthenticated' do
- context 'and request is sent by gitlab-workhorse to authorize the request' do
- before do
- header_for_upload_authorize(forked_project)
- end
-
- it 'responds with 401' do
- expect(lfs_router_forked_noauth.try_call.first).to eq(401)
- end
- end
-
- context 'and request is sent by gitlab-workhorse to finalize the upload' do
- before do
- headers_for_upload_finalize(forked_project)
- end
-
- it 'responds with 401' do
- expect(lfs_router_forked_noauth.try_call.first).to eq(401)
- end
- end
- end
- end
-
- describe 'and second project not related to fork or a source project' do
- let(:second_project) { create(:project) }
- let(:lfs_router_second_project) { new_lfs_router(second_project, user) }
-
- before do
- public_project.lfs_objects << lfs_object
- headers_for_upload_finalize(second_project)
- end
-
- context 'when pushing the same lfs object to the second project' do
- before do
- second_project.team << [user, :master]
- end
-
- it 'responds with 200 and links the lfs object to the project' do
- expect(lfs_router_second_project.try_call.first).to eq(200)
- expect(lfs_object.projects.pluck(:id)).to include(second_project.id, public_project.id)
- end
- end
- end
- end
- end
-
- def enable_lfs
- allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
- end
-
- def authorize(user)
- ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password)
- end
-
- def new_lfs_router(project, user)
- Gitlab::Lfs::Router.new(project, user, request)
- end
-
- def header_for_upload_authorize(project)
- env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}/authorize"
- end
-
- def headers_for_upload_finalize(project)
- env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}"
- env["HTTP_X_GITLAB_LFS_TMP"] = "#{sample_oid}6e561c9d4"
- end
-
- def fork_project(project, user, object = nil)
- allow(RepositoryForkWorker).to receive(:perform_async).and_return(true)
- Projects::ForkService.new(project, user, {}).execute
- end
-end
diff --git a/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb b/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb
index e520a968999..4d2aa03e722 100644
--- a/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
describe Gitlab::Metrics::SidekiqMiddleware do
let(:middleware) { described_class.new }
+ let(:message) { { 'args' => ['test'], 'enqueued_at' => Time.new(2016, 6, 23, 6, 59).to_f } }
describe '#call' do
it 'tracks the transaction' do
@@ -11,9 +12,23 @@ describe Gitlab::Metrics::SidekiqMiddleware do
with('TestWorker#perform').
and_call_original
+ expect_any_instance_of(Gitlab::Metrics::Transaction).to receive(:set).with(:sidekiq_queue_duration, instance_of(Float))
expect_any_instance_of(Gitlab::Metrics::Transaction).to receive(:finish)
- middleware.call(worker, 'test', :test) { nil }
+ middleware.call(worker, message, :test) { nil }
+ end
+
+ it 'tracks the transaction (for messages without `enqueued_at`)' do
+ worker = double(:worker, class: double(:class, name: 'TestWorker'))
+
+ expect(Gitlab::Metrics::Transaction).to receive(:new).
+ with('TestWorker#perform').
+ and_call_original
+
+ expect_any_instance_of(Gitlab::Metrics::Transaction).to receive(:set).with(:sidekiq_queue_duration, instance_of(Float))
+ expect_any_instance_of(Gitlab::Metrics::Transaction).to receive(:finish)
+
+ middleware.call(worker, {}, :test) { nil }
end
end
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 84c21ceefd9..56bf08e7041 100644
--- a/spec/lib/gitlab/saml/user_spec.rb
+++ b/spec/lib/gitlab/saml/user_spec.rb
@@ -164,7 +164,14 @@ describe Gitlab::Saml::User, lib: true do
end
context 'and LDAP user has an account already' do
- let!(:existing_user) { create(:omniauth_user, email: 'john@mail.com', extern_uid: 'uid=user1,ou=People,dc=example', provider: 'ldapmain', username: 'john') }
+ before do
+ create(:omniauth_user,
+ email: 'john@mail.com',
+ extern_uid: 'uid=user1,ou=People,dc=example',
+ provider: 'ldapmain',
+ username: 'john')
+ end
+
it 'adds the omniauth identity to the LDAP account' do
saml_user.save
@@ -177,6 +184,15 @@ describe Gitlab::Saml::User, lib: true do
{ provider: 'saml', extern_uid: uid }
])
end
+
+ it 'saves successfully on subsequent tries, when both identities are present' do
+ saml_user.save
+ local_saml_user = described_class.new(auth_hash)
+ local_saml_user.save
+
+ expect(local_saml_user.gl_user).to be_valid
+ expect(local_saml_user.gl_user).to be_persisted
+ end
end
context 'user has SAML user, and wants to add their LDAP identity' do
@@ -198,7 +214,6 @@ describe Gitlab::Saml::User, lib: true do
end
end
end
-
end
describe 'blocking' do
diff --git a/spec/lib/gitlab/gitignore_spec.rb b/spec/lib/gitlab/template/gitignore_spec.rb
index 72baa516cc4..bc0ec9325cc 100644
--- a/spec/lib/gitlab/gitignore_spec.rb
+++ b/spec/lib/gitlab/template/gitignore_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
-describe Gitlab::Gitignore do
- subject { Gitlab::Gitignore }
+describe Gitlab::Template::Gitignore do
+ subject { described_class }
describe '.all' do
it 'strips the gitignore suffix' do
@@ -24,7 +24,7 @@ describe Gitlab::Gitignore do
it 'returns the Gitignore object of a valid file' do
ruby = subject.find('Ruby')
- expect(ruby).to be_a Gitlab::Gitignore
+ expect(ruby).to be_a Gitlab::Template::Gitignore
expect(ruby.name).to eq('Ruby')
end
end
diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb
index bf11472407a..a826b24419a 100644
--- a/spec/lib/gitlab/url_builder_spec.rb
+++ b/spec/lib/gitlab/url_builder_spec.rb
@@ -43,9 +43,9 @@ describe Gitlab::UrlBuilder, lib: true do
end
end
- context 'on a CommitDiff' do
+ context 'on a Commit Diff' do
it 'returns a proper URL' do
- note = build_stubbed(:note_on_commit_diff)
+ note = build_stubbed(:diff_note_on_commit)
url = described_class.build(note)
@@ -75,10 +75,10 @@ describe Gitlab::UrlBuilder, lib: true do
end
end
- context 'on a MergeRequestDiff' do
+ context 'on a MergeRequest Diff' do
it 'returns a proper URL' do
merge_request = create(:merge_request, iid: 42)
- note = build_stubbed(:note_on_merge_request_diff, noteable: merge_request)
+ note = build_stubbed(:diff_note_on_merge_request, noteable: merge_request)
url = described_class.build(note)
diff --git a/spec/lib/gitlab/url_sanitizer_spec.rb b/spec/lib/gitlab/url_sanitizer_spec.rb
index de55334118f..2cb74629da8 100644
--- a/spec/lib/gitlab/url_sanitizer_spec.rb
+++ b/spec/lib/gitlab/url_sanitizer_spec.rb
@@ -45,6 +45,12 @@ describe Gitlab::UrlSanitizer, lib: true do
expect(filtered_content).to include("user@server:project.git")
end
+
+ it 'returns an empty string for invalid URLs' do
+ filtered_content = sanitize_url('ssh://')
+
+ expect(filtered_content).to include("repository '' not found")
+ end
end
describe '#sanitized_url' do
@@ -64,5 +70,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/user_access_spec.rb b/spec/lib/gitlab/user_access_spec.rb
new file mode 100644
index 00000000000..aa9ec243498
--- /dev/null
+++ b/spec/lib/gitlab/user_access_spec.rb
@@ -0,0 +1,88 @@
+require 'spec_helper'
+
+describe Gitlab::UserAccess, lib: true do
+ let(:access) { Gitlab::UserAccess.new(user, project: project) }
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ describe 'can_push_to_branch?' do
+ describe 'push to none protected branch' do
+ it 'returns true if user is a master' do
+ project.team << [user, :master]
+ expect(access.can_push_to_branch?('random_branch')).to be_truthy
+ end
+
+ it 'returns true if user is a developer' do
+ project.team << [user, :developer]
+ expect(access.can_push_to_branch?('random_branch')).to be_truthy
+ end
+
+ it 'returns false if user is a reporter' do
+ project.team << [user, :reporter]
+ expect(access.can_push_to_branch?('random_branch')).to be_falsey
+ end
+ end
+
+ describe 'push to protected branch' do
+ let(:branch) { create :protected_branch, project: project }
+
+ it 'returns true if user is a master' do
+ project.team << [user, :master]
+ expect(access.can_push_to_branch?(branch.name)).to be_truthy
+ end
+
+ it 'returns false if user is a developer' do
+ project.team << [user, :developer]
+ expect(access.can_push_to_branch?(branch.name)).to be_falsey
+ end
+
+ it 'returns false if user is a reporter' do
+ project.team << [user, :reporter]
+ expect(access.can_push_to_branch?(branch.name)).to be_falsey
+ end
+ end
+
+ describe 'push to protected branch if allowed for developers' do
+ before do
+ @branch = create :protected_branch, project: project, developers_can_push: true
+ end
+
+ it 'returns true if user is a master' do
+ project.team << [user, :master]
+ expect(access.can_push_to_branch?(@branch.name)).to be_truthy
+ end
+
+ it 'returns true if user is a developer' do
+ project.team << [user, :developer]
+ expect(access.can_push_to_branch?(@branch.name)).to be_truthy
+ end
+
+ it 'returns false if user is a reporter' do
+ project.team << [user, :reporter]
+ expect(access.can_push_to_branch?(@branch.name)).to be_falsey
+ end
+ end
+
+ describe 'merge to protected branch if allowed for developers' do
+ before do
+ @branch = create :protected_branch, project: project, developers_can_merge: true
+ end
+
+ it 'returns true if user is a master' do
+ project.team << [user, :master]
+ expect(access.can_merge_to_branch?(@branch.name)).to be_truthy
+ end
+
+ it 'returns true if user is a developer' do
+ project.team << [user, :developer]
+ expect(access.can_merge_to_branch?(@branch.name)).to be_truthy
+ end
+
+ it 'returns false if user is a reporter' do
+ project.team << [user, :reporter]
+ expect(access.can_merge_to_branch?(@branch.name)).to be_falsey
+ end
+ 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 1e6eb20ab39..0a9b10bebea 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -401,23 +401,56 @@ describe Notify do
end
describe 'project access requested' do
- let(:project) { create(:project) }
- let(:user) { create(:user) }
- let(:project_member) do
- project.request_access(user)
- project.members.request.find_by(user_id: user.id)
+ context 'for a project in a user namespace' do
+ let(:project) { create(:project).tap { |p| p.team << [p.owner, :master, p.owner] } }
+ let(:user) { create(:user) }
+ let(:project_member) do
+ project.request_access(user)
+ project.requesters.find_by(user_id: user.id)
+ end
+ subject { Notify.member_access_requested_email('project', project_member.id) }
+
+ it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'it should not have Gmail Actions links'
+ it_behaves_like "a user cannot unsubscribe through footer link"
+
+ it 'contains all the useful information' do
+ to_emails = subject.header[:to].addrs
+ expect(to_emails.size).to eq(1)
+ expect(to_emails[0].address).to eq(project.members.owners_and_masters.first.user.notification_email)
+
+ is_expected.to have_subject "Request to join the #{project.name_with_namespace} project"
+ is_expected.to have_body_text /#{project.name_with_namespace}/
+ is_expected.to have_body_text /#{namespace_project_project_members_url(project.namespace, project)}/
+ is_expected.to have_body_text /#{project_member.human_access}/
+ end
end
- subject { Notify.member_access_requested_email('project', project_member.id) }
- it_behaves_like 'an email sent from GitLab'
- it_behaves_like 'it should not have Gmail Actions links'
- it_behaves_like "a user cannot unsubscribe through footer link"
+ context 'for a project in a group' do
+ let(:group_owner) { create(:user) }
+ let(:group) { create(:group).tap { |g| g.add_owner(group_owner) } }
+ let(:project) { create(:project, namespace: group) }
+ let(:user) { create(:user) }
+ let(:project_member) do
+ project.request_access(user)
+ project.requesters.find_by(user_id: user.id)
+ end
+ subject { Notify.member_access_requested_email('project', project_member.id) }
- it 'contains all the useful information' do
- is_expected.to have_subject "Request to join the #{project.name_with_namespace} project"
- is_expected.to have_body_text /#{project.name_with_namespace}/
- is_expected.to have_body_text /#{namespace_project_project_members_url(project.namespace, project)}/
- is_expected.to have_body_text /#{project_member.human_access}/
+ it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'it should not have Gmail Actions links'
+ it_behaves_like "a user cannot unsubscribe through footer link"
+
+ it 'contains all the useful information' do
+ to_emails = subject.header[:to].addrs
+ expect(to_emails.size).to eq(1)
+ expect(to_emails[0].address).to eq(group.members.owners_and_masters.first.user.notification_email)
+
+ is_expected.to have_subject "Request to join the #{project.name_with_namespace} project"
+ is_expected.to have_body_text /#{project.name_with_namespace}/
+ is_expected.to have_body_text /#{namespace_project_project_members_url(project.namespace, project)}/
+ is_expected.to have_body_text /#{project_member.human_access}/
+ end
end
end
@@ -426,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) }
@@ -651,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) }
@@ -672,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) }
@@ -915,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) }
@@ -951,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
@@ -959,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
@@ -977,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
@@ -995,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
@@ -1020,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) }
@@ -1051,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 5d1fa8226e5..06d984c7a40 100644
--- a/spec/models/build_spec.rb
+++ b/spec/models/build_spec.rb
@@ -2,7 +2,12 @@ require 'spec_helper'
describe Ci::Build, models: true do
let(:project) { create(:project) }
- let(:pipeline) { create(:ci_pipeline, project: project) }
+
+ let(:pipeline) do
+ create(:ci_pipeline, project: project,
+ sha: project.commit.id)
+ end
+
let(:build) { create(:ci_build, pipeline: pipeline) }
it { is_expected.to validate_presence_of :ref }
@@ -36,32 +41,44 @@ describe Ci::Build, models: true do
subject { build.ignored? }
context 'if build is not allowed to fail' do
- before { build.allow_failure = false }
+ before do
+ build.allow_failure = false
+ end
context 'and build.status is success' do
- before { build.status = 'success' }
+ before do
+ build.status = 'success'
+ end
it { is_expected.to be_falsey }
end
context 'and build.status is failed' do
- before { build.status = 'failed' }
+ before do
+ build.status = 'failed'
+ end
it { is_expected.to be_falsey }
end
end
context 'if build is allowed to fail' do
- before { build.allow_failure = true }
+ before do
+ build.allow_failure = true
+ end
context 'and build.status is success' do
- before { build.status = 'success' }
+ before do
+ build.status = 'success'
+ end
it { is_expected.to be_falsey }
end
context 'and build.status is failed' do
- before { build.status = 'failed' }
+ before do
+ build.status = 'failed'
+ end
it { is_expected.to be_truthy }
end
@@ -75,7 +92,9 @@ describe Ci::Build, models: true do
context 'if build.trace contains text' do
let(:text) { 'example output' }
- before { build.trace = text }
+ before do
+ build.trace = text
+ end
it { is_expected.to include(text) }
it { expect(subject.length).to be >= text.length }
@@ -172,23 +191,25 @@ describe Ci::Build, models: true do
end
describe '#variables' do
- context 'returns variables' do
- subject { build.variables }
+ let(:predefined_variables) do
+ [
+ { key: :CI_BUILD_NAME, value: 'test', public: true },
+ { key: :CI_BUILD_STAGE, value: 'test', public: true },
+ ]
+ end
- let(:predefined_variables) do
- [
- { key: :CI_BUILD_NAME, value: 'test', public: true },
- { key: :CI_BUILD_STAGE, value: 'stage', public: true },
- ]
- end
+ subject { build.variables }
+ context 'returns variables' do
let(:yaml_variables) do
[
{ key: :DB_NAME, value: 'postgres', public: true }
]
end
- before { build.update_attributes(stage: 'stage') }
+ before do
+ build.yaml_variables = yaml_variables
+ end
it { is_expected.to eq(predefined_variables + yaml_variables) }
@@ -199,7 +220,9 @@ describe Ci::Build, models: true do
]
end
- before { build.update_attributes(tag: true) }
+ before do
+ build.update_attributes(tag: true)
+ end
it { is_expected.to eq(tag_variable + predefined_variables + yaml_variables) }
end
@@ -237,73 +260,54 @@ describe Ci::Build, models: true do
it { is_expected.to eq(predefined_variables + predefined_trigger_variable + yaml_variables + secure_variables + trigger_variables) }
end
-
- context 'when job variables are defined' do
- ##
- # Job-level variables are defined in gitlab_ci.yml fixture
- #
- context 'when job variables are unique' do
- let(:build) { create(:ci_build, name: 'staging') }
-
- it 'includes job variables' do
- expect(subject).to include(
- { key: :KEY1, value: 'value1', public: true },
- { key: :KEY2, value: 'value2', public: true }
- )
- end
- end
- end
end
end
- end
-
- describe '#can_be_served?' do
- let(:runner) { create(:ci_runner) }
-
- before { build.project.runners << runner }
- context 'when runner does not have tags' do
- it 'can handle builds without tags' do
- expect(build.can_be_served?(runner)).to be_truthy
+ context 'when yaml_variables is undefined' do
+ before do
+ build.yaml_variables = nil
end
- it 'cannot handle build with tags' do
- build.tag_list = ['aa']
- expect(build.can_be_served?(runner)).to be_falsey
- end
- end
+ context 'use from gitlab-ci.yml' do
+ before do
+ stub_ci_pipeline_yaml_file(config)
+ end
- context 'when runner has tags' do
- before { runner.tag_list = ['bb', 'cc'] }
+ context 'if config is not found' do
+ let(:config) { nil }
- shared_examples 'tagged build picker' do
- it 'can handle build with matching tags' do
- build.tag_list = ['bb']
- expect(build.can_be_served?(runner)).to be_truthy
+ it { is_expected.to eq(predefined_variables) }
end
- it 'cannot handle build without matching tags' do
- build.tag_list = ['aa']
- expect(build.can_be_served?(runner)).to be_falsey
- end
- end
+ context 'if config does not have a questioned job' do
+ let(:config) do
+ YAML.dump({
+ test_other: {
+ script: 'Hello World'
+ }
+ })
+ end
- context 'when runner can pick untagged jobs' do
- it 'can handle builds without tags' do
- expect(build.can_be_served?(runner)).to be_truthy
+ it { is_expected.to eq(predefined_variables) }
end
- it_behaves_like 'tagged build picker'
- end
-
- context 'when runner can not pick untagged jobs' do
- before { runner.run_untagged = false }
+ context 'if config has variables' do
+ let(:config) do
+ YAML.dump({
+ test: {
+ script: 'Hello World',
+ variables: {
+ KEY: 'value'
+ }
+ }
+ })
+ end
+ let(:variables) do
+ [{ key: :KEY, value: 'value', public: true }]
+ end
- it 'can not handle builds without tags' do
- expect(build.can_be_served?(runner)).to be_falsey
+ it { is_expected.to eq(predefined_variables + variables) }
end
-
- it_behaves_like 'tagged build picker'
end
end
end
@@ -348,10 +352,9 @@ describe Ci::Build, models: true do
end
it 'that cannot handle build' do
- expect_any_instance_of(Ci::Build).to receive(:can_be_served?).and_return(false)
+ expect_any_instance_of(Ci::Runner).to receive(:can_pick?).and_return(false)
is_expected.to be_falsey
end
-
end
end
@@ -360,7 +363,9 @@ describe Ci::Build, models: true do
%w(pending).each do |state|
context "if commit_status.status is #{state}" do
- before { build.status = state }
+ before do
+ build.status = state
+ end
it { is_expected.to be_truthy }
@@ -379,7 +384,9 @@ describe Ci::Build, models: true do
%w(success failed canceled running).each do |state|
context "if commit_status.status is #{state}" do
- before { build.status = state }
+ before do
+ build.status = state
+ end
it { is_expected.to be_falsey }
end
@@ -390,7 +397,10 @@ describe Ci::Build, models: true do
subject { build.artifacts? }
context 'artifacts archive does not exist' do
- before { build.update_attributes(artifacts_file: nil) }
+ before do
+ build.update_attributes(artifacts_file: nil)
+ end
+
it { is_expected.to be_falsy }
end
@@ -623,7 +633,9 @@ describe Ci::Build, models: true do
let!(:build) { create(:ci_build, :trace, :success, :artifacts) }
describe '#erase' do
- before { build.erase(erased_by: user) }
+ before do
+ build.erase(erased_by: user)
+ end
context 'erased by user' do
let!(:user) { create(:user, username: 'eraser') }
@@ -660,7 +672,9 @@ describe Ci::Build, models: true do
end
context 'build has been erased' do
- before { build.erase }
+ before do
+ build.erase
+ end
it { is_expected.to be true }
end
@@ -668,7 +682,9 @@ describe Ci::Build, models: true do
context 'metadata and build trace are not available' do
let!(:build) { create(:ci_build, :success, :artifacts) }
- before { build.remove_artifacts_metadata! }
+ before do
+ build.remove_artifacts_metadata!
+ end
describe '#erase' do
it 'should not raise error' do
@@ -678,4 +694,144 @@ describe Ci::Build, models: true do
end
end
end
+
+ describe '#commit' do
+ it 'returns commit pipeline has been created for' 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
+
+ describe '#manual?' do
+ before do
+ build.update(when: value)
+ end
+
+ subject { build.manual? }
+
+ context 'when is set to manual' do
+ let(:value) { 'manual' }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when set to something else' do
+ let(:value) { 'something else' }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe '#other_actions' do
+ let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
+ let!(:other_build) { create(:ci_build, :manual, pipeline: pipeline, name: 'other action') }
+
+ subject { build.other_actions }
+
+ it 'returns other actions' do
+ is_expected.to contain_exactly(other_build)
+ end
+ end
+
+ describe '#play' do
+ let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
+
+ subject { build.play }
+
+ it 'enques a build' do
+ is_expected.to be_pending
+ is_expected.to eq(build)
+ end
+
+ context 'for success build' do
+ before { build.queue }
+
+ it 'creates a new build' do
+ is_expected.to be_pending
+ is_expected.not_to eq(build)
+ end
+ end
+ end
+
+ describe '#when' do
+ subject { build.when }
+
+ context 'if is undefined' do
+ before do
+ build.when = nil
+ end
+
+ context 'use from gitlab-ci.yml' do
+ before do
+ stub_ci_pipeline_yaml_file(config)
+ end
+
+ context 'if config is not found' do
+ let(:config) { nil }
+
+ it { is_expected.to eq('on_success') }
+ end
+
+ context 'if config does not have a questioned job' do
+ let(:config) do
+ YAML.dump({
+ test_other: {
+ script: 'Hello World'
+ }
+ })
+ end
+
+ it { is_expected.to eq('on_success') }
+ end
+
+ context 'if config has when' do
+ let(:config) do
+ YAML.dump({
+ test: {
+ script: 'Hello World',
+ when: 'always'
+ }
+ })
+ end
+
+ it { is_expected.to eq('always') }
+ end
+ end
+ 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/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 34507cf5083..c29e4811385 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -5,9 +5,12 @@ describe Ci::Pipeline, models: true do
let(:pipeline) { FactoryGirl.create :ci_pipeline, project: project }
it { is_expected.to belong_to(:project) }
+ it { is_expected.to belong_to(:user) }
+
it { is_expected.to have_many(:statuses) }
it { is_expected.to have_many(:trigger_requests) }
it { is_expected.to have_many(:builds) }
+
it { is_expected.to validate_presence_of :sha }
it { is_expected.to validate_presence_of :status }
@@ -15,7 +18,7 @@ describe Ci::Pipeline, models: true do
it { is_expected.to respond_to :git_author_email }
it { is_expected.to respond_to :short_sha }
- describe :valid_commit_sha do
+ describe '#valid_commit_sha' do
context 'commit.sha can not start with 00000000' do
before do
pipeline.sha = '0' * 40
@@ -26,7 +29,7 @@ describe Ci::Pipeline, models: true do
end
end
- describe :short_sha do
+ describe '#short_sha' do
subject { pipeline.short_sha }
it 'has 8 items' do
@@ -35,10 +38,10 @@ describe Ci::Pipeline, models: true do
it { expect(pipeline.sha).to start_with(subject) }
end
- describe :create_next_builds do
+ describe '#create_next_builds' do
end
- describe :retried do
+ describe '#retried' do
subject { pipeline.retried }
before do
@@ -51,7 +54,7 @@ describe Ci::Pipeline, models: true do
end
end
- describe :create_builds do
+ describe '#create_builds' do
let!(:pipeline) { FactoryGirl.create :ci_pipeline, project: project, ref: 'master', tag: false }
def create_builds(trigger_request = nil)
@@ -257,6 +260,68 @@ describe Ci::Pipeline, models: true do
expect(pipeline.reload.status).to eq('canceled')
end
end
+
+ context 'when listing manual actions' do
+ let(:yaml) do
+ {
+ stages: ["build", "test", "test_failure", "deploy", "cleanup"],
+ build: {
+ stage: "build",
+ script: "BUILD",
+ },
+ test: {
+ stage: "test",
+ script: "TEST",
+ },
+ test_failure: {
+ stage: "test_failure",
+ script: "ON test failure",
+ when: "on_failure",
+ },
+ deploy: {
+ stage: "deploy",
+ script: "PUBLISH",
+ },
+ production: {
+ stage: "deploy",
+ script: "PUBLISH",
+ when: "manual",
+ },
+ cleanup: {
+ stage: "cleanup",
+ script: "TIDY UP",
+ when: "always",
+ },
+ clear_cache: {
+ stage: "cleanup",
+ script: "CLEAR CACHE",
+ when: "manual",
+ }
+ }
+ end
+
+ it 'returns only for skipped builds' do
+ # currently all builds are created
+ expect(create_builds).to be_truthy
+ expect(manual_actions).to be_empty
+
+ # succeed stage build
+ pipeline.builds.running_or_pending.each(&:success)
+ expect(manual_actions).to be_empty
+
+ # succeed stage test
+ pipeline.builds.running_or_pending.each(&:success)
+ expect(manual_actions).to be_one # production
+
+ # succeed stage deploy
+ pipeline.builds.running_or_pending.each(&:success)
+ expect(manual_actions).to be_many # production and clear cache
+ end
+
+ def manual_actions
+ pipeline.manual_actions
+ end
+ end
end
context 'when no builds created' do
@@ -413,4 +478,28 @@ describe Ci::Pipeline, models: true do
end
end
end
+
+ describe '#manual_actions' do
+ subject { pipeline.manual_actions }
+
+ it 'when none defined' do
+ is_expected.to be_empty
+ end
+
+ context 'when action defined' do
+ let!(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy') }
+
+ it 'returns one action' do
+ is_expected.to contain_exactly(manual)
+ end
+
+ context 'there are multiple of the same name' do
+ let!(:manual2) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy') }
+
+ it 'returns latest one' do
+ is_expected.to contain_exactly(manual2)
+ end
+ end
+ end
+ end
end
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index 5d04d8ffcff..ef65eb99328 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -20,34 +20,36 @@ describe Ci::Runner, models: true do
end
describe '#display_name' do
- it 'should return the description if it has a value' do
+ it 'returns the description if it has a value' do
runner = FactoryGirl.build(:ci_runner, description: 'Linux/Ruby-1.9.3-p448')
expect(runner.display_name).to eq 'Linux/Ruby-1.9.3-p448'
end
- it 'should return the token if it does not have a description' do
+ it 'returns the token if it does not have a description' do
runner = FactoryGirl.create(:ci_runner)
expect(runner.display_name).to eq runner.description
end
- it 'should return the token if the description is an empty string' do
+ it 'returns the token if the description is an empty string' do
runner = FactoryGirl.build(:ci_runner, description: '', token: 'token')
expect(runner.display_name).to eq runner.token
end
end
- describe :assign_to do
+ describe '#assign_to' do
let!(:project) { FactoryGirl.create :empty_project }
let!(:shared_runner) { FactoryGirl.create(:ci_runner, :shared) }
- before { shared_runner.assign_to(project) }
+ before do
+ shared_runner.assign_to(project)
+ end
it { expect(shared_runner).to be_specific }
it { expect(shared_runner.projects).to eq([project]) }
it { expect(shared_runner.only_for?(project)).to be_truthy }
end
- describe :online do
+ describe '.online' do
subject { Ci::Runner.online }
before do
@@ -58,60 +60,269 @@ describe Ci::Runner, models: true do
it { is_expected.to eq([@runner2])}
end
- describe :online? do
+ describe '#online?' do
let(:runner) { FactoryGirl.create(:ci_runner, :shared) }
subject { runner.online? }
context 'never contacted' do
- before { runner.contacted_at = nil }
+ before do
+ runner.contacted_at = nil
+ end
it { is_expected.to be_falsey }
end
context 'contacted long time ago time' do
- before { runner.contacted_at = 1.year.ago }
+ before do
+ runner.contacted_at = 1.year.ago
+ end
it { is_expected.to be_falsey }
end
context 'contacted 1s ago' do
- before { runner.contacted_at = 1.second.ago }
+ before do
+ runner.contacted_at = 1.second.ago
+ end
it { is_expected.to be_truthy }
end
end
- describe :status do
+ describe '#can_pick?' do
+ let(:project) { create(:project) }
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:build) { create(:ci_build, pipeline: pipeline) }
+ let(:runner) { create(:ci_runner) }
+
+ before do
+ build.project.runners << runner
+ end
+
+ context 'when runner does not have tags' do
+ it 'can handle builds without tags' do
+ expect(runner.can_pick?(build)).to be_truthy
+ end
+
+ it 'cannot handle build with tags' do
+ build.tag_list = ['aa']
+
+ expect(runner.can_pick?(build)).to be_falsey
+ end
+ end
+
+ context 'when runner has tags' do
+ before do
+ runner.tag_list = ['bb', 'cc']
+ end
+
+ shared_examples 'tagged build picker' do
+ it 'can handle build with matching tags' do
+ build.tag_list = ['bb']
+
+ expect(runner.can_pick?(build)).to be_truthy
+ end
+
+ it 'cannot handle build without matching tags' do
+ build.tag_list = ['aa']
+
+ expect(runner.can_pick?(build)).to be_falsey
+ end
+ end
+
+ context 'when runner can pick untagged jobs' do
+ it 'can handle builds without tags' do
+ expect(runner.can_pick?(build)).to be_truthy
+ end
+
+ it_behaves_like 'tagged build picker'
+ end
+
+ context 'when runner cannot pick untagged jobs' do
+ before do
+ runner.run_untagged = false
+ end
+
+ it 'cannot handle builds without tags' do
+ expect(runner.can_pick?(build)).to be_falsey
+ end
+
+ it_behaves_like 'tagged build picker'
+ end
+ end
+
+ context 'when runner is locked' do
+ before do
+ runner.locked = true
+ end
+
+ shared_examples 'locked build picker' do
+ context 'when runner cannot pick untagged jobs' do
+ before do
+ runner.run_untagged = false
+ end
+
+ it 'cannot handle builds without tags' do
+ expect(runner.can_pick?(build)).to be_falsey
+ end
+ end
+
+ context 'when having runner tags' do
+ before do
+ runner.tag_list = ['bb', 'cc']
+ end
+
+ it 'cannot handle it for builds without matching tags' do
+ build.tag_list = ['aa']
+
+ expect(runner.can_pick?(build)).to be_falsey
+ end
+ end
+ end
+
+ context 'when serving the same project' do
+ it 'can handle it' do
+ expect(runner.can_pick?(build)).to be_truthy
+ end
+
+ it_behaves_like 'locked build picker'
+
+ context 'when having runner tags' do
+ before do
+ runner.tag_list = ['bb', 'cc']
+ build.tag_list = ['bb']
+ end
+
+ it 'can handle it for matching tags' do
+ expect(runner.can_pick?(build)).to be_truthy
+ end
+ end
+ end
+
+ context 'serving a different project' do
+ before do
+ runner.runner_projects.destroy_all
+ end
+
+ it 'cannot handle it' do
+ expect(runner.can_pick?(build)).to be_falsey
+ end
+
+ it_behaves_like 'locked build picker'
+
+ context 'when having runner tags' do
+ before do
+ runner.tag_list = ['bb', 'cc']
+ build.tag_list = ['bb']
+ end
+
+ it 'cannot handle it for matching tags' do
+ expect(runner.can_pick?(build)).to be_falsey
+ end
+ end
+ end
+ end
+ end
+
+ describe '#status' do
let(:runner) { FactoryGirl.create(:ci_runner, :shared, contacted_at: 1.second.ago) }
subject { runner.status }
context 'never connected' do
- before { runner.contacted_at = nil }
+ before do
+ runner.contacted_at = nil
+ end
it { is_expected.to eq(:not_connected) }
end
context 'contacted 1s ago' do
- before { runner.contacted_at = 1.second.ago }
+ before do
+ runner.contacted_at = 1.second.ago
+ end
it { is_expected.to eq(:online) }
end
context 'contacted long time ago' do
- before { runner.contacted_at = 1.year.ago }
+ before do
+ runner.contacted_at = 1.year.ago
+ end
it { is_expected.to eq(:offline) }
end
context 'inactive' do
- before { runner.active = false }
+ before do
+ runner.active = false
+ end
it { is_expected.to eq(:paused) }
end
end
+ describe '.assignable_for' do
+ let(:runner) { create(:ci_runner) }
+ let(:project) { create(:project) }
+ let(:another_project) { create(:project) }
+
+ before do
+ project.runners << runner
+ end
+
+ context 'with shared runners' do
+ before do
+ runner.update(is_shared: true)
+ end
+
+ context 'does not give owned runner' do
+ subject { Ci::Runner.assignable_for(project) }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'does not give shared runner' do
+ subject { Ci::Runner.assignable_for(another_project) }
+
+ it { is_expected.to be_empty }
+ end
+ end
+
+ context 'with unlocked runner' do
+ context 'does not give owned runner' do
+ subject { Ci::Runner.assignable_for(project) }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'does give a specific runner' do
+ subject { Ci::Runner.assignable_for(another_project) }
+
+ it { is_expected.to contain_exactly(runner) }
+ end
+ end
+
+ context 'with locked runner' do
+ before do
+ runner.update(locked: true)
+ end
+
+ context 'does not give owned runner' do
+ subject { Ci::Runner.assignable_for(project) }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'does not give a locked runner' do
+ subject { Ci::Runner.assignable_for(another_project) }
+
+ it { is_expected.to be_empty }
+ end
+ end
+ end
+
describe "belongs_to_one_project?" do
it "returns false if there are two projects runner assigned to" do
runner = FactoryGirl.create(:ci_runner)
diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb
index 98f60087cf5..4e7833c3162 100644
--- a/spec/models/ci/variable_spec.rb
+++ b/spec/models/ci/variable_spec.rb
@@ -9,7 +9,7 @@ describe Ci::Variable, models: true do
subject.value = secret_value
end
- describe :value do
+ describe '#value' do
it 'stores the encrypted value' do
expect(subject.encrypted_value).not_to be_nil
end
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index beca8708c9d..ba02d5fe977 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -207,4 +207,16 @@ eos
expect(commit.participants).to include(note1.author, note2.author)
end
end
+
+ describe '#uri_type' do
+ it 'returns the URI type at the given path' do
+ expect(commit.uri_type('files/html')).to be(:tree)
+ expect(commit.uri_type('files/images/logo-black.png')).to be(:raw)
+ expect(commit.uri_type('files/js/application.js')).to be(:blob)
+ end
+
+ it "returns nil if the path doesn't exists" do
+ expect(commit.uri_type('this/path/doesnt/exist')).to be_nil
+ end
+ end
end
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index 8fb605fff8a..ff6371ad685 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -1,8 +1,13 @@
require 'spec_helper'
describe CommitStatus, models: true do
- let(:pipeline) { FactoryGirl.create :ci_pipeline }
- let(:commit_status) { FactoryGirl.create :commit_status, pipeline: pipeline }
+ let(:project) { create(:project) }
+
+ let(:pipeline) do
+ create(:ci_pipeline, project: project, sha: project.commit.id)
+ end
+
+ let(:commit_status) { create(:commit_status, pipeline: pipeline) }
it { is_expected.to belong_to(:pipeline) }
it { is_expected.to belong_to(:user) }
@@ -13,20 +18,20 @@ describe CommitStatus, models: true do
it { is_expected.to delegate_method(:sha).to(:pipeline) }
it { is_expected.to delegate_method(:short_sha).to(:pipeline) }
-
+
it { is_expected.to respond_to :success? }
it { is_expected.to respond_to :failed? }
it { is_expected.to respond_to :running? }
it { is_expected.to respond_to :pending? }
- describe :author do
+ describe '#author' do
subject { commit_status.author }
before { commit_status.author = User.new }
it { is_expected.to eq(commit_status.user) }
end
- describe :started? do
+ describe '#started?' do
subject { commit_status.started? }
context 'without started_at' do
@@ -52,7 +57,7 @@ describe CommitStatus, models: true do
end
end
- describe :active? do
+ describe '#active?' do
subject { commit_status.active? }
%w(pending running).each do |state|
@@ -72,7 +77,7 @@ describe CommitStatus, models: true do
end
end
- describe :complete? do
+ describe '#complete?' do
subject { commit_status.complete? }
%w(success failed canceled).each do |state|
@@ -92,7 +97,7 @@ describe CommitStatus, models: true do
end
end
- describe :duration do
+ describe '#duration' do
subject { commit_status.duration }
it { is_expected.to eq(120.0) }
@@ -116,8 +121,8 @@ describe CommitStatus, models: true do
it { is_expected.to be > 0.0 }
end
end
-
- describe :latest do
+
+ describe '.latest' do
subject { CommitStatus.latest.order(:id) }
before do
@@ -133,7 +138,7 @@ describe CommitStatus, models: true do
end
end
- describe :running_or_pending do
+ describe '.running_or_pending' do
subject { CommitStatus.running_or_pending.order(:id) }
before do
@@ -172,10 +177,10 @@ describe CommitStatus, models: true do
describe '#stages' do
before do
- FactoryGirl.create :commit_status, pipeline: pipeline, stage: 'build', stage_idx: 0, status: 'success'
- FactoryGirl.create :commit_status, pipeline: pipeline, stage: 'build', stage_idx: 0, status: 'failed'
- FactoryGirl.create :commit_status, pipeline: pipeline, stage: 'deploy', stage_idx: 2, status: 'running'
- FactoryGirl.create :commit_status, pipeline: pipeline, stage: 'test', stage_idx: 1, status: 'success'
+ create :commit_status, pipeline: pipeline, stage: 'build', name: 'linux', stage_idx: 0, status: 'success'
+ create :commit_status, pipeline: pipeline, stage: 'build', name: 'mac', stage_idx: 0, status: 'failed'
+ create :commit_status, pipeline: pipeline, stage: 'deploy', name: 'staging', stage_idx: 2, status: 'running'
+ create :commit_status, pipeline: pipeline, stage: 'test', name: 'rspec', stage_idx: 1, status: 'success'
end
context 'stages list' do
@@ -187,7 +192,7 @@ describe CommitStatus, models: true do
end
context 'stages with statuses' do
- subject { CommitStatus.where(pipeline: pipeline).stages_status }
+ subject { CommitStatus.where(pipeline: pipeline).latest.stages_status }
it 'return list of stages with statuses' do
is_expected.to eq({
@@ -196,6 +201,26 @@ describe CommitStatus, models: true do
'deploy' => 'running'
})
end
+
+ context 'when build is retried' do
+ before do
+ create :commit_status, pipeline: pipeline, stage: 'build', name: 'mac', stage_idx: 0, status: 'success'
+ end
+
+ it 'ignores a previous state' do
+ is_expected.to eq({
+ 'build' => 'success',
+ 'test' => 'success',
+ 'deploy' => 'running'
+ })
+ end
+ end
+ end
+ end
+
+ describe '#commit' do
+ it 'returns commit pipeline has been created for' do
+ expect(commit_status.commit).to eq project.commit
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 efbcbf72f76..60e4bbc8564 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -154,8 +154,21 @@ describe Issue, "Issuable" do
expect(issues).to match_array([issue1, issue2, issue, issue3])
end
end
- end
+ context 'when all of the results are level on the sort key' do
+ let!(:issues) do
+ 10.times { create(:issue, project: project) }
+ end
+
+ it 'has no duplicates across pages' do
+ sorted_issue_ids = 1.upto(10).map do |i|
+ project.issues.sort('milestone_due_desc').page(i).per(1).first.id
+ end
+
+ expect(sorted_issue_ids).to eq(sorted_issue_ids.uniq)
+ end
+ end
+ end
describe '#subscribed?' do
context 'user is not a participant in the issue' do
diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb
index cb33edde820..5e652660e2c 100644
--- a/spec/models/concerns/mentionable_spec.rb
+++ b/spec/models/concerns/mentionable_spec.rb
@@ -7,7 +7,7 @@ describe Mentionable do
nil
end
- describe :references do
+ describe 'references' do
let(:project) { create(:project) }
it 'excludes JIRA references' do
@@ -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/participable_spec.rb b/spec/models/concerns/participable_spec.rb
index 7e4ea0f2d66..a9f4ef9ee5e 100644
--- a/spec/models/concerns/participable_spec.rb
+++ b/spec/models/concerns/participable_spec.rb
@@ -37,6 +37,16 @@ describe Participable, models: true do
expect(participants).to include(user3)
end
+ it 'caches the raw list of participants' do
+ instance = model.new
+ user1 = build(:user)
+
+ expect(instance).to receive(:raw_participants).once
+
+ instance.participants(user1)
+ instance.participants(user1)
+ end
+
it 'supports attributes returning another Participable' do
other_model = Class.new { include Participable }
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/deployment_spec.rb b/spec/models/deployment_spec.rb
index b273018707f..7df3df4bb9e 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -11,6 +11,7 @@ describe Deployment, models: true do
it { is_expected.to delegate_method(:name).to(:environment).with_prefix }
it { is_expected.to delegate_method(:commit).to(:project) }
it { is_expected.to delegate_method(:commit_title).to(:commit).as(:try) }
+ it { is_expected.to delegate_method(:manual_actions).to(:deployable).as(:try) }
it { is_expected.to validate_presence_of(:ref) }
it { is_expected.to validate_presence_of(:sha) }
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..f94987dcaff 100644
--- a/spec/models/forked_project_link_spec.rb
+++ b/spec/models/forked_project_link_spec.rb
@@ -18,19 +18,17 @@ describe ForkedProjectLink, "add link on fork" do
end
end
-describe :forked_from_project do
+describe '#forked?' do
let(:forked_project_link) { build(:forked_project_link) }
let(:project_from) { create(:project) }
let(:project_to) { create(:project, forked_project_link: forked_project_link) }
-
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/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb
index c4e781dd1dc..615cfe3142b 100644
--- a/spec/models/generic_commit_status_spec.rb
+++ b/spec/models/generic_commit_status_spec.rb
@@ -4,33 +4,33 @@ describe GenericCommitStatus, models: true do
let(:pipeline) { FactoryGirl.create :ci_pipeline }
let(:generic_commit_status) { FactoryGirl.create :generic_commit_status, pipeline: pipeline }
- describe :context do
+ describe '#context' do
subject { generic_commit_status.context }
before { generic_commit_status.context = 'my_context' }
it { is_expected.to eq(generic_commit_status.name) }
end
- describe :tags do
+ describe '#tags' do
subject { generic_commit_status.tags }
it { is_expected.to eq([:external]) }
end
- describe :set_default_values do
+ describe 'set_default_values' do
before do
generic_commit_status.context = nil
generic_commit_status.stage = nil
generic_commit_status.save
end
- describe :context do
+ describe '#context' do
subject { generic_commit_status.context }
it { is_expected.not_to be_nil }
end
- describe :stage do
+ describe '#stage' do
subject { generic_commit_status.stage }
it { is_expected.not_to be_nil }
diff --git a/spec/models/global_milestone_spec.rb b/spec/models/global_milestone_spec.rb
index 197c99cd007..ae77ec5b348 100644
--- a/spec/models/global_milestone_spec.rb
+++ b/spec/models/global_milestone_spec.rb
@@ -14,7 +14,7 @@ describe GlobalMilestone, models: true do
let(:milestone2_project2) { create(:milestone, title: "VD-123", project: project2) }
let(:milestone2_project3) { create(:milestone, title: "VD-123", project: project3) }
- describe :build_collection do
+ describe '.build_collection' do
before do
milestones =
[
@@ -42,7 +42,7 @@ describe GlobalMilestone, models: true do
end
end
- describe :initialize do
+ describe '#initialize' do
before do
milestones =
[
@@ -63,7 +63,7 @@ describe GlobalMilestone, models: true do
end
end
- describe :safe_title do
+ describe '#safe_title' do
let(:milestone) { create(:milestone, title: "git / test", project: project1) }
it 'should strip out slashes and spaces' do
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 2c19aa3f67f..266c46213a6 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
@@ -68,22 +97,22 @@ describe Group, models: true do
end
end
- describe :users do
+ describe '#users' do
it { expect(group.users).to eq(group.owners) }
end
- describe :human_name do
+ describe '#human_name' do
it { expect(group.human_name).to eq(group.name) }
end
- describe :add_users do
+ describe '#add_user' do
let(:user) { create(:user) }
before { group.add_user(user, GroupMember::MASTER) }
it { expect(group.group_members.masters.map(&:user)).to include(user) }
end
- describe :add_users do
+ describe '#add_users' do
let(:user) { create(:user) }
before { group.add_users([user.id], GroupMember::GUEST) }
@@ -95,7 +124,7 @@ describe Group, models: true do
end
end
- describe :avatar_type do
+ describe '#avatar_type' do
let(:user) { create(:user) }
before { group.add_user(user, GroupMember::MASTER) }
diff --git a/spec/models/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/key_spec.rb b/spec/models/key_spec.rb
index 26fbedbef2f..49cf3d8633a 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -26,7 +26,7 @@ describe Key, models: true do
end
end
- context "validation of uniqueness" do
+ context "validation of uniqueness (based on fingerprint uniqueness)" do
let(:user) { create(:user) }
it "accepts the key once" do
diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb
index dad2628651b..f37f44a608e 100644
--- a/spec/models/label_spec.rb
+++ b/spec/models/label_spec.rb
@@ -32,21 +32,20 @@ describe Label, models: true do
it 'should validate title' do
expect(label).not_to allow_value('G,ITLAB').for(:title)
- expect(label).not_to allow_value('G?ITLAB').for(:title)
- expect(label).not_to allow_value('G&ITLAB').for(:title)
expect(label).not_to allow_value('').for(:title)
expect(label).to allow_value('GITLAB').for(:title)
expect(label).to allow_value('gitlab').for(:title)
+ expect(label).to allow_value('G?ITLAB').for(:title)
+ expect(label).to allow_value('G&ITLAB').for(:title)
expect(label).to allow_value("customer's request").for(:title)
end
end
- describe "#title" do
- let(:label) { create(:label, title: "<b>test</b>") }
-
- it "sanitizes title" do
- expect(label.title).to eq("test")
+ describe '#title' do
+ it 'sanitizes title' do
+ label = described_class.new(title: '<b>foo & bar?</b>')
+ expect(label.title).to eq('foo & bar?')
end
end
diff --git a/spec/models/legacy_diff_note_spec.rb b/spec/models/legacy_diff_note_spec.rb
index b2d06853886..d23fc06c3ad 100644
--- a/spec/models/legacy_diff_note_spec.rb
+++ b/spec/models/legacy_diff_note_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe LegacyDiffNote, models: true do
describe "Commit diff line notes" do
- let!(:note) { create(:note_on_commit_diff, note: "+1 from me") }
+ let!(:note) { create(:legacy_diff_note_on_commit, note: "+1 from me") }
let!(:commit) { note.noteable }
it "should save a valid note" do
@@ -16,25 +16,25 @@ describe LegacyDiffNote, models: true do
end
describe '#active?' do
- it 'is always true when the note has no associated diff' do
- note = build(:note_on_merge_request_diff)
+ it 'is always true when the note has no associated diff line' do
+ note = build(:legacy_diff_note_on_merge_request)
- expect(note).to receive(:diff).and_return(nil)
+ expect(note).to receive(:diff_line).and_return(nil)
expect(note).to be_active
end
it 'is never true when the note has no noteable associated' do
- note = build(:note_on_merge_request_diff)
+ note = build(:legacy_diff_note_on_merge_request)
- expect(note).to receive(:diff).and_return(double)
+ expect(note).to receive(:diff_line).and_return(double)
expect(note).to receive(:noteable).and_return(nil)
expect(note).not_to be_active
end
it 'returns the memoized value if defined' do
- note = build(:note_on_merge_request_diff)
+ note = build(:legacy_diff_note_on_merge_request)
note.instance_variable_set(:@active, 'foo')
expect(note).not_to receive(:find_noteable_diff)
@@ -45,9 +45,9 @@ describe LegacyDiffNote, models: true do
context 'for a merge request noteable' do
it 'is false when noteable has no matching diff' do
merge = build_stubbed(:merge_request, :simple)
- note = build(:note_on_merge_request_diff, noteable: merge)
+ note = build(:legacy_diff_note_on_merge_request, noteable: merge)
- allow(note).to receive(:diff).and_return(double)
+ allow(note).to receive(:diff_line).and_return(double)
expect(note).to receive(:find_noteable_diff).and_return(nil)
expect(note).not_to be_active
@@ -63,9 +63,9 @@ describe LegacyDiffNote, models: true do
code = Gitlab::Diff::LineCode.generate(diff.new_path, line.new_pos, line.old_pos)
# We're persisting in order to trigger the set_diff callback
- note = create(:note_on_merge_request_diff, noteable: merge,
- line_code: code,
- project: merge.source_project)
+ note = create(:legacy_diff_note_on_merge_request, noteable: merge,
+ line_code: code,
+ project: merge.source_project)
# Make sure we don't get a false positive from a guard clause
expect(note).to receive(:find_noteable_diff).and_call_original
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index 3ed3202ac6c..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 }
@@ -134,18 +118,6 @@ describe Member, models: true do
it { is_expected.to respond_to(:user_email) }
end
- describe 'Callbacks' do
- describe 'after_destroy :post_decline_request, if: :request?' do
- let(:member) { create(:project_member, requested_at: Time.now.utc) }
-
- it 'calls #post_decline_request' do
- expect(member).to receive(:post_decline_request)
-
- member.destroy
- end
- end
- end
-
describe ".add_user" do
let!(:user) { create(:user) }
let(:project) { create(:project) }
diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb
index eeb74a462ac..18439cac2a4 100644
--- a/spec/models/members/group_member_spec.rb
+++ b/spec/models/members/group_member_spec.rb
@@ -61,16 +61,6 @@ describe GroupMember, models: true do
end
end
- describe '#post_decline_request' do
- it 'calls NotificationService.decline_group_access_request' do
- member = create(:group_member, user: build_stubbed(:user), requested_at: Time.now)
-
- expect_any_instance_of(NotificationService).to receive(:decline_group_access_request)
-
- member.__send__(:post_decline_request)
- end
- end
-
describe '#real_source_type' do
subject { create(:group_member).real_source_type }
diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb
index 1e466f9c620..ba622dfb9be 100644
--- a/spec/models/members/project_member_spec.rb
+++ b/spec/models/members/project_member_spec.rb
@@ -101,7 +101,7 @@ describe ProjectMember, models: true do
end
end
- describe :add_users_into_projects do
+ describe '.add_users_into_projects' do
before do
@project_1 = create :project
@project_2 = create :project
@@ -119,12 +119,11 @@ 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
- describe :truncate_teams do
+ describe '.truncate_teams' do
before do
@project_1 = create :project
@project_2 = create :project
@@ -152,15 +151,5 @@ describe ProjectMember, models: true do
member.__send__(:after_accept_request)
end
end
-
- describe '#post_decline_request' do
- it 'calls NotificationService.decline_project_access_request' do
- member = create(:project_member, user: build_stubbed(:user), requested_at: Time.now)
-
- expect_any_instance_of(NotificationService).to receive(:decline_project_access_request)
-
- member.__send__(:post_decline_request)
- end
- end
end
end
diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb
new file mode 100644
index 00000000000..9a637c94fbe
--- /dev/null
+++ b/spec/models/merge_request_diff_spec.rb
@@ -0,0 +1,47 @@
+require 'spec_helper'
+
+describe MergeRequestDiff, models: true do
+ describe '#diffs' do
+ let(:mr) { create(:merge_request, :with_diffs) }
+ let(:mr_diff) { mr.merge_request_diff }
+
+ context 'when the :ignore_whitespace_change option is set' do
+ it 'creates a new compare object instead of loading from the DB' do
+ expect(mr_diff).not_to receive(:load_diffs)
+ expect(Gitlab::Git::Compare).to receive(:new).and_call_original
+
+ mr_diff.diffs(ignore_whitespace_change: true)
+ end
+ end
+
+ context 'when the raw diffs are empty' do
+ before { mr_diff.update_attributes(st_diffs: '') }
+
+ it 'returns an empty DiffCollection' do
+ expect(mr_diff.diffs).to be_a(Gitlab::Git::DiffCollection)
+ expect(mr_diff.diffs).to be_empty
+ end
+ end
+
+ context 'when the raw diffs exist' do
+ it 'returns the diffs' do
+ expect(mr_diff.diffs).to be_a(Gitlab::Git::DiffCollection)
+ expect(mr_diff.diffs).not_to be_empty
+ end
+
+ context 'when the :paths option is set' do
+ let(:diffs) { mr_diff.diffs(paths: ['files/ruby/popen.rb', 'files/ruby/popen.rb']) }
+
+ it 'only returns diffs that match the (old path, new path) given' do
+ expect(diffs.map(&:new_path)).to contain_exactly('files/ruby/popen.rb')
+ end
+
+ it 'uses the diffs from the DB' do
+ expect(mr_diff).to receive(:load_diffs)
+
+ diffs
+ end
+ end
+ end
+ end
+end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 3b199f4d98d..c8ad7ab3e7f 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe MergeRequest, models: true do
+ include RepoHelpers
+
subject { create(:merge_request) }
describe 'associations' do
@@ -62,7 +64,7 @@ describe MergeRequest, models: true do
end
end
- describe '#target_sha' do
+ describe '#target_branch_sha' do
context 'when the target branch does not exist anymore' do
let(:project) { create(:project) }
@@ -73,32 +75,32 @@ describe MergeRequest, models: true do
end
it 'returns nil' do
- expect(subject.target_sha).to be_nil
+ expect(subject.target_branch_sha).to be_nil
end
end
end
- describe '#source_sha' do
+ describe '#source_branch_sha' do
let(:last_branch_commit) { subject.source_project.repository.commit(subject.source_branch) }
context 'with diffs' do
subject { create(:merge_request, :with_diffs) }
it 'returns the sha of the source branch last commit' do
- expect(subject.source_sha).to eq(last_branch_commit.sha)
+ expect(subject.source_branch_sha).to eq(last_branch_commit.sha)
end
end
context 'without diffs' do
subject { create(:merge_request, :without_diffs) }
it 'returns the sha of the source branch last commit' do
- expect(subject.source_sha).to eq(last_branch_commit.sha)
+ expect(subject.source_branch_sha).to eq(last_branch_commit.sha)
end
end
context 'when the merge request is being created' do
subject { build(:merge_request, source_branch: nil, compare_commits: []) }
it 'returns nil' do
- expect(subject.source_sha).to be_nil
+ expect(subject.source_branch_sha).to be_nil
end
end
end
@@ -114,6 +116,31 @@ describe MergeRequest, models: true do
end
end
+ describe '#diffs' do
+ let(:merge_request) { build(:merge_request) }
+ let(:options) { { paths: ['a/b', 'b/a', 'c/*'] } }
+
+ context 'when there are MR diffs' do
+ it 'delegates to the MR diffs' do
+ merge_request.merge_request_diff = MergeRequestDiff.new
+
+ expect(merge_request.merge_request_diff).to receive(:diffs).with(options)
+
+ merge_request.diffs(options)
+ end
+ end
+
+ context 'when there are no MR diffs' do
+ it 'delegates to the compare object' do
+ merge_request.compare = double(:compare)
+
+ expect(merge_request.compare).to receive(:diffs).with(options)
+
+ merge_request.diffs(options)
+ end
+ end
+ end
+
describe "#mr_and_commit_notes" do
let!(:merge_request) { create(:merge_request) }
@@ -252,12 +279,14 @@ describe MergeRequest, models: true do
end
it "can be removed if the last commit is the head of the source branch" do
- allow(subject.source_project).to receive(:commit).and_return(subject.last_commit)
+ allow(subject.source_project).to receive(:commit).and_return(subject.diff_head_commit)
expect(subject.can_remove_source_branch?(user)).to be_truthy
end
it "cannot be removed if the last commit is not also the head of the source branch" do
+ subject.source_branch = "lfs"
+
expect(subject.can_remove_source_branch?(user)).to be_falsey
end
end
@@ -363,7 +392,7 @@ describe MergeRequest, models: true do
and_return(2)
subject.diverged_commits_count
- allow(subject).to receive(:source_sha).and_return('123abc')
+ allow(subject).to receive(:source_branch_sha).and_return('123abc')
subject.diverged_commits_count
end
@@ -373,7 +402,7 @@ describe MergeRequest, models: true do
and_return(2)
subject.diverged_commits_count
- allow(subject).to receive(:target_sha).and_return('123abc')
+ allow(subject).to receive(:target_branch_sha).and_return('123abc')
subject.diverged_commits_count
end
end
@@ -392,11 +421,10 @@ describe MergeRequest, models: true do
describe '#pipeline' do
describe 'when the source project exists' do
- it 'returns the latest commit' do
- commit = double(:commit, id: '123abc')
+ it 'returns the latest pipeline' do
pipeline = double(:ci_pipeline, ref: 'master')
- allow(subject).to receive(:last_commit).and_return(commit)
+ allow(subject).to receive(:diff_head_sha).and_return('123abc')
expect(subject.source_project).to receive(:pipeline).
with('123abc', 'master').
@@ -464,7 +492,7 @@ describe MergeRequest, models: true do
context 'when it is not broken and has no conflicts' do
it 'is marked as mergeable' do
allow(subject).to receive(:broken?) { false }
- allow(project).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 +509,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 +636,42 @@ describe MergeRequest, models: true do
end
end
end
+
+ describe "#reload_diff" do
+ let(:note) { create(:diff_note_on_merge_request, project: subject.project, noteable: subject) }
+
+ let(:commit) { subject.project.commit(sample_commit.id) }
+
+ it "reloads the diff content" do
+ expect(subject.merge_request_diff).to receive(:reload_content)
+
+ subject.reload_diff
+ end
+
+ it "updates diff note positions" do
+ old_diff_refs = subject.diff_refs
+
+ merge_request_diff = subject.merge_request_diff
+
+ # Update merge_request_diff so that #diff_refs will return commit.diff_refs
+ allow(merge_request_diff).to receive(:reload_content) do
+ merge_request_diff.base_commit_sha = commit.parent_id
+ merge_request_diff.start_commit_sha = commit.parent_id
+ merge_request_diff.head_commit_sha = commit.sha
+ end
+
+ expect(Notes::DiffPositionUpdateService).to receive(:new).with(
+ subject.project,
+ nil,
+ old_diff_refs: old_diff_refs,
+ new_diff_refs: commit.diff_refs,
+ paths: note.position.paths
+ ).and_call_original
+ expect_any_instance_of(Notes::DiffPositionUpdateService).to receive(:execute).with(note)
+
+ expect_any_instance_of(DiffNote).to receive(:save).once
+
+ subject.reload_diff
+ end
+ end
end
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index 1e18c788b50..d661dc0e59a 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -70,7 +70,7 @@ describe Milestone, models: true do
end
end
- describe :expired? do
+ describe '#expired?' do
context "expired" do
before do
allow(milestone).to receive(:due_date).and_return(Date.today.prev_year)
@@ -88,7 +88,7 @@ describe Milestone, models: true do
end
end
- describe :percent_complete do
+ describe '#percent_complete' do
before do
allow(milestone).to receive_messages(
closed_items_count: 3,
@@ -111,11 +111,11 @@ describe Milestone, models: true do
it { expect(milestone.is_empty?(user)).to be_falsey }
end
- describe :can_be_closed? do
+ describe '#can_be_closed?' do
it { expect(milestone.can_be_closed?).to be_truthy }
end
- describe :total_items_count do
+ describe '#total_items_count' do
before do
create :closed_issue, milestone: milestone
create :merge_request, milestone: milestone
@@ -126,7 +126,7 @@ describe Milestone, models: true do
end
end
- describe :can_be_closed? do
+ describe '#can_be_closed?' do
before do
milestone = create :milestone
create :closed_issue, milestone: milestone
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 4e68ac5e63a..a162da0208e 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -18,11 +18,11 @@ describe Namespace, models: true do
it { is_expected.to respond_to(:to_param) }
end
- describe :to_param do
+ describe '#to_param' do
it { expect(namespace.to_param).to eq(namespace.path) }
end
- describe :human_name do
+ describe '#human_name' do
it { expect(namespace.human_name).to eq(namespace.owner_name) }
end
@@ -54,9 +54,10 @@ describe Namespace, models: true do
end
end
- describe :move_dir do
+ describe '#move_dir' do
before do
@namespace = create :namespace
+ @project = create :project, namespace: @namespace
allow(@namespace).to receive(:path_changed?).and_return(true)
end
@@ -87,12 +88,17 @@ 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
- describe :find_by_path_or_name do
+ describe '.find_by_path_or_name' do
before do
@namespace = create(:namespace, name: 'WoW', path: 'woW')
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..7d0697dab42 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
@@ -222,6 +226,20 @@ describe Note, models: true do
it "returns false" do
expect(note.cross_reference_not_visible_for?(private_user)).to be_falsy
end
+
+ it "returns false if user visible reference count set" do
+ note.user_visible_reference_count = 1
+
+ expect(note).not_to receive(:reference_mentionables)
+ expect(note.cross_reference_not_visible_for?(ext_issue.author)).to be_falsy
+ end
+
+ it "returns true if ref count is 0" do
+ note.user_visible_reference_count = 0
+
+ expect(note).not_to receive(:reference_mentionables)
+ expect(note.cross_reference_not_visible_for?(ext_issue.author)).to be_truthy
+ end
end
describe 'clear_blank_line_code!' do
diff --git a/spec/models/notification_setting_spec.rb b/spec/models/notification_setting_spec.rb
index df336a6effe..d58673413c8 100644
--- a/spec/models/notification_setting_spec.rb
+++ b/spec/models/notification_setting_spec.rb
@@ -38,4 +38,21 @@ RSpec.describe NotificationSetting, type: :model do
end
end
end
+
+ describe '#for_projects' do
+ let(:user) { create(:user) }
+
+ before do
+ 1.upto(4) do |i|
+ setting = create(:notification_setting, user: user)
+
+ setting.project.update_attributes(pending_delete: true) if i.even?
+ end
+ end
+
+ it 'excludes projects pending delete' do
+ expect(user.notification_settings.for_projects).to all(have_attributes(project: an_instance_of(Project)))
+ expect(user.notification_settings.for_projects.map(&:project)).to all(have_attributes(pending_delete: false))
+ end
+ end
end
diff --git a/spec/models/project_security_spec.rb b/spec/models/project_security_spec.rb
index e12258c0874..2142c7c13ef 100644
--- a/spec/models/project_security_spec.rb
+++ b/spec/models/project_security_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Project, models: true do
- describe :authorization do
+ describe 'authorization' do
before do
@p1 = create(:project)
diff --git a/spec/models/project_services/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/buildkite_service_spec.rb b/spec/models/project_services/buildkite_service_spec.rb
index 60364df2015..0866e1532dd 100644
--- a/spec/models/project_services/buildkite_service_spec.rb
+++ b/spec/models/project_services/buildkite_service_spec.rb
@@ -57,7 +57,7 @@ describe BuildkiteService, models: true do
)
end
- describe :webhook_url do
+ describe '#webhook_url' do
it 'returns the webhook url' do
expect(@service.webhook_url).to eq(
'https://webhook.buildkite.com/deliver/secret-sauce-webhook-token'
@@ -65,7 +65,7 @@ describe BuildkiteService, models: true do
end
end
- describe :commit_status_path do
+ describe '#commit_status_path' do
it 'returns the correct status page' do
expect(@service.commit_status_path('2ab7834c')).to eq(
'https://gitlab.buildkite.com/status/secret-sauce-status-token.json?commit=2ab7834c'
@@ -73,7 +73,7 @@ describe BuildkiteService, models: true do
end
end
- describe :build_page do
+ describe '#build_page' do
it 'returns the correct build page' do
expect(@service.build_page('2ab7834c', nil)).to eq(
'https://buildkite.com/account-name/example-project/builds?commit=2ab7834c'
diff --git a/spec/models/project_services/builds_email_service_spec.rb b/spec/models/project_services/builds_email_service_spec.rb
index 236df8f047d..ca2cd8aa551 100644
--- a/spec/models/project_services/builds_email_service_spec.rb
+++ b/spec/models/project_services/builds_email_service_spec.rb
@@ -23,6 +23,44 @@ describe BuildsEmailService do
end
end
+ describe '#test_data' do
+ let(:build) { create(:ci_build) }
+ let(:project) { build.project }
+ let(:user) { create(:user) }
+
+ before { project.team << [user, :developer] }
+
+ it 'builds test data' do
+ data = subject.test_data(project)
+
+ expect(data[:object_kind]).to eq("build")
+ end
+ end
+
+ describe '#test' do
+ it 'sends email' do
+ data = Gitlab::BuildDataBuilder.build(create(:ci_build))
+ subject.recipients = 'test@gitlab.com'
+
+ expect(BuildEmailWorker).to receive(:perform_async)
+
+ subject.test(data)
+ end
+
+ context 'notify only failed builds is true' do
+ it 'sends email' do
+ data = Gitlab::BuildDataBuilder.build(create(:ci_build))
+ data[:build_status] = "success"
+ subject.recipients = 'test@gitlab.com'
+
+ expect(subject).not_to receive(:notify_only_broken_builds)
+ expect(BuildEmailWorker).to receive(:perform_async)
+
+ subject.test(data)
+ end
+ end
+ end
+
describe '#execute' do
it 'sends email' do
subject.recipients = 'test@gitlab.com'
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..9dc34276f18 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,71 @@ 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 'does not allow an invalid URI as import_url' do
+ project2 = build(:project, import_url: 'invalid://')
+
+ expect(project2).not_to be_valid
+ end
+
+ it 'does allow a valid URI as import_url' do
+ project2 = build(:project, import_url: 'ssh://test@gitlab.com/project.git')
+
+ expect(project2).to be_valid
+ end
+
+ it 'allows an empty URI' do
+ project2 = build(:project, import_url: '')
+
+ expect(project2).to be_valid
+ end
+
+ it 'does not produce import data on an empty URI' do
+ project2 = build(:project, import_url: '')
+
+ expect(project2.import_data).to be_nil
+ end
+
+ it 'does not produce import data on an invalid URI' do
+ project2 = build(:project, import_url: 'test://')
+
+ expect(project2.import_data).to be_nil
+ end
end
describe 'default_scope' do
@@ -110,6 +206,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')
@@ -200,7 +314,7 @@ describe Project, models: true do
end
end
- describe :update_merge_requests do
+ describe '#update_merge_requests' do
let(:project) { create(:project) }
let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
let(:key) { create(:key, user_id: project.owner.id) }
@@ -216,7 +330,7 @@ describe Project, models: true do
it 'should update merge request commits with new one if pushed to source branch' do
project.update_merge_requests(prev_commit_id, commit_id, "refs/heads/#{merge_request.source_branch}", key.user)
merge_request.reload
- expect(merge_request.last_commit.id).to eq(commit_id)
+ expect(merge_request.diff_head_sha).to eq(commit_id)
end
end
@@ -249,7 +363,7 @@ describe Project, models: true do
end
end
- describe :to_param do
+ describe '#to_param' do
context 'with namespace' do
before do
@group = create :group, name: 'gitlab'
@@ -260,7 +374,7 @@ describe Project, models: true do
end
end
- describe :repository do
+ describe '#repository' do
let(:project) { create(:project) }
it 'should return valid repo' do
@@ -268,7 +382,7 @@ describe Project, models: true do
end
end
- describe :default_issues_tracker? do
+ describe '#default_issues_tracker?' do
let(:project) { create(:project) }
let(:ext_project) { create(:redmine_project) }
@@ -281,7 +395,7 @@ describe Project, models: true do
end
end
- describe :external_issue_tracker do
+ describe '#external_issue_tracker' do
let(:project) { create(:project) }
let(:ext_project) { create(:redmine_project) }
@@ -322,7 +436,7 @@ describe Project, models: true do
end
end
- describe :cache_has_external_issue_tracker do
+ describe '#cache_has_external_issue_tracker' do
let(:project) { create(:project) }
it 'stores true if there is any external_issue_tracker' do
@@ -344,7 +458,7 @@ describe Project, models: true do
end
end
- describe :open_branches do
+ describe '#open_branches' do
let(:project) { create(:project) }
before do
@@ -353,6 +467,14 @@ describe Project, models: true do
it { expect(project.open_branches.map(&:name)).to include('feature') }
it { expect(project.open_branches.map(&:name)).not_to include('master') }
+
+ it "includes branches matching a protected branch wildcard" do
+ expect(project.open_branches.map(&:name)).to include('feature')
+
+ create(:protected_branch, name: 'feat*', project: project)
+
+ expect(Project.find(project.id).open_branches.map(&:name)).to include('feature')
+ end
end
describe '#star_count' do
@@ -413,7 +535,7 @@ describe Project, models: true do
end
end
- describe :avatar_type do
+ describe '#avatar_type' do
let(:project) { create(:project) }
it 'should be true if avatar is image' do
@@ -427,7 +549,7 @@ describe Project, models: true do
end
end
- describe :avatar_url do
+ describe '#avatar_url' do
subject { project.avatar_url }
let(:project) { create(:project) }
@@ -464,7 +586,7 @@ describe Project, models: true do
end
end
- describe :pipeline do
+ describe '#pipeline' do
let(:project) { create :project }
let(:pipeline) { create :ci_pipeline, project: project, ref: 'master' }
@@ -484,7 +606,7 @@ describe Project, models: true do
end
end
- describe :builds_enabled do
+ describe '#builds_enabled' do
let(:project) { create :project }
before { project.builds_enabled = true }
@@ -553,6 +675,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) }
@@ -571,7 +708,7 @@ describe Project, models: true do
end
end
- describe :any_runners do
+ describe '#any_runners' do
let(:project) { create(:empty_project, shared_runners_enabled: shared_runners_enabled) }
let(:specific_runner) { create(:ci_runner) }
let(:shared_runner) { create(:ci_runner, :shared) }
@@ -708,12 +845,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 +942,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 +952,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 +975,67 @@ describe Project, models: true do
describe '#protected_branch?' do
let(:project) { create(:empty_project) }
- it 'returns true when a branch is a protected branch' do
+ it 'returns true when the branch matches a protected branch via direct match' do
project.protected_branches.create!(name: 'foo')
expect(project.protected_branch?('foo')).to eq(true)
end
- it 'returns false when a branch is not a protected branch' do
+ it 'returns true when the branch matches a protected branch via wildcard match' do
+ project.protected_branches.create!(name: 'production/*')
+
+ expect(project.protected_branch?('production/some-branch')).to eq(true)
+ end
+
+ it 'returns false when the branch does not match a protected branch via direct match' do
expect(project.protected_branch?('foo')).to eq(false)
end
+
+ it 'returns false when the branch does not match a protected branch via wildcard match' do
+ project.protected_branches.create!(name: 'production/*')
+
+ expect(project.protected_branch?('staging/some-branch')).to eq(false)
+ end
+ end
+
+ describe "#developers_can_push_to_protected_branch?" do
+ let(:project) { create(:empty_project) }
+
+ context "when the branch matches a protected branch via direct match" do
+ it "returns true if 'Developers can Push' is turned on" do
+ create(:protected_branch, name: "production", project: project, developers_can_push: true)
+
+ expect(project.developers_can_push_to_protected_branch?('production')).to be true
+ end
+
+ it "returns false if 'Developers can Push' is turned off" do
+ create(:protected_branch, name: "production", project: project, developers_can_push: false)
+
+ expect(project.developers_can_push_to_protected_branch?('production')).to be false
+ end
+ end
+
+ context "when the branch matches a protected branch via wilcard match" do
+ it "returns true if 'Developers can Push' is turned on" do
+ create(:protected_branch, name: "production/*", project: project, developers_can_push: true)
+
+ expect(project.developers_can_push_to_protected_branch?('production/some-branch')).to be true
+ end
+
+ it "returns false if 'Developers can Push' is turned off" do
+ create(:protected_branch, name: "production/*", project: project, developers_can_push: false)
+
+ expect(project.developers_can_push_to_protected_branch?('production/some-branch')).to be false
+ end
+ end
+
+ context "when the branch does not match a protected branch" do
+ it "returns false" do
+ create(:protected_branch, name: "production/*", project: project, developers_can_push: true)
+
+ expect(project.developers_can_push_to_protected_branch?('staging/some-branch')).to be false
+ end
+ end
end
describe '#container_registry_path_with_namespace' do
diff --git a/spec/models/protected_branch_spec.rb b/spec/models/protected_branch_spec.rb
index b523834c6e9..8bf0d24a128 100644
--- a/spec/models/protected_branch_spec.rb
+++ b/spec/models/protected_branch_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe ProtectedBranch, models: true do
+ subject { build_stubbed(:protected_branch) }
+
describe 'Associations' do
it { is_expected.to belong_to(:project) }
end
@@ -12,4 +14,127 @@ describe ProtectedBranch, models: true do
it { is_expected.to validate_presence_of(:project) }
it { is_expected.to validate_presence_of(:name) }
end
+
+ describe "#matches?" do
+ context "when the protected branch setting is not a wildcard" do
+ let(:protected_branch) { build(:protected_branch, name: "production/some-branch") }
+
+ it "returns true for branch names that are an exact match" do
+ expect(protected_branch.matches?("production/some-branch")).to be true
+ end
+
+ it "returns false for branch names that are not an exact match" do
+ expect(protected_branch.matches?("staging/some-branch")).to be false
+ end
+ end
+
+ context "when the protected branch name contains wildcard(s)" do
+ context "when there is a single '*'" do
+ let(:protected_branch) { build(:protected_branch, name: "production/*") }
+
+ it "returns true for branch names matching the wildcard" do
+ expect(protected_branch.matches?("production/some-branch")).to be true
+ expect(protected_branch.matches?("production/")).to be true
+ end
+
+ it "returns false for branch names not matching the wildcard" do
+ expect(protected_branch.matches?("staging/some-branch")).to be false
+ expect(protected_branch.matches?("production")).to be false
+ end
+ end
+
+ context "when the wildcard contains regex symbols other than a '*'" do
+ let(:protected_branch) { build(:protected_branch, name: "pro.duc.tion/*") }
+
+ it "returns true for branch names matching the wildcard" do
+ expect(protected_branch.matches?("pro.duc.tion/some-branch")).to be true
+ end
+
+ it "returns false for branch names not matching the wildcard" do
+ expect(protected_branch.matches?("production/some-branch")).to be false
+ expect(protected_branch.matches?("proXducYtion/some-branch")).to be false
+ end
+ end
+
+ context "when there are '*'s at either end" do
+ let(:protected_branch) { build(:protected_branch, name: "*/production/*") }
+
+ it "returns true for branch names matching the wildcard" do
+ expect(protected_branch.matches?("gitlab/production/some-branch")).to be true
+ expect(protected_branch.matches?("/production/some-branch")).to be true
+ expect(protected_branch.matches?("gitlab/production/")).to be true
+ expect(protected_branch.matches?("/production/")).to be true
+ end
+
+ it "returns false for branch names not matching the wildcard" do
+ expect(protected_branch.matches?("gitlabproductionsome-branch")).to be false
+ expect(protected_branch.matches?("production/some-branch")).to be false
+ expect(protected_branch.matches?("gitlab/production")).to be false
+ expect(protected_branch.matches?("production")).to be false
+ end
+ end
+
+ context "when there are arbitrarily placed '*'s" do
+ let(:protected_branch) { build(:protected_branch, name: "pro*duction/*/gitlab/*") }
+
+ it "returns true for branch names matching the wildcard" do
+ expect(protected_branch.matches?("production/some-branch/gitlab/second-branch")).to be true
+ expect(protected_branch.matches?("proXYZduction/some-branch/gitlab/second-branch")).to be true
+ expect(protected_branch.matches?("proXYZduction/gitlab/gitlab/gitlab")).to be true
+ expect(protected_branch.matches?("proXYZduction//gitlab/")).to be true
+ expect(protected_branch.matches?("proXYZduction/some-branch/gitlab/")).to be true
+ expect(protected_branch.matches?("proXYZduction//gitlab/some-branch")).to be true
+ end
+
+ it "returns false for branch names not matching the wildcard" do
+ expect(protected_branch.matches?("production/some-branch/not-gitlab/second-branch")).to be false
+ expect(protected_branch.matches?("prodXYZuction/some-branch/gitlab/second-branch")).to be false
+ expect(protected_branch.matches?("proXYZduction/gitlab/some-branch/gitlab")).to be false
+ expect(protected_branch.matches?("proXYZduction/gitlab//")).to be false
+ expect(protected_branch.matches?("proXYZduction/gitlab/")).to be false
+ expect(protected_branch.matches?("proXYZduction//some-branch/gitlab")).to be false
+ end
+ end
+ end
+ end
+
+ describe "#matching" do
+ context "for direct matches" do
+ it "returns a list of protected branches matching the given branch name" do
+ production = create(:protected_branch, name: "production")
+ staging = create(:protected_branch, name: "staging")
+
+ expect(ProtectedBranch.matching("production")).to include(production)
+ expect(ProtectedBranch.matching("production")).not_to include(staging)
+ end
+
+ it "accepts a list of protected branches to search from, so as to avoid a DB call" do
+ production = build(:protected_branch, name: "production")
+ staging = build(:protected_branch, name: "staging")
+
+ expect(ProtectedBranch.matching("production")).to be_empty
+ expect(ProtectedBranch.matching("production", protected_branches: [production, staging])).to include(production)
+ expect(ProtectedBranch.matching("production", protected_branches: [production, staging])).not_to include(staging)
+ end
+ end
+
+ context "for wildcard matches" do
+ it "returns a list of protected branches matching the given branch name" do
+ production = create(:protected_branch, name: "production/*")
+ staging = create(:protected_branch, name: "staging/*")
+
+ expect(ProtectedBranch.matching("production/some-branch")).to include(production)
+ expect(ProtectedBranch.matching("production/some-branch")).not_to include(staging)
+ end
+
+ it "accepts a list of protected branches to search from, so as to avoid a DB call" do
+ production = build(:protected_branch, name: "production/*")
+ staging = build(:protected_branch, name: "staging/*")
+
+ expect(ProtectedBranch.matching("production/some-branch")).to be_empty
+ expect(ProtectedBranch.matching("production/some-branch", protected_branches: [production, staging])).to include(production)
+ expect(ProtectedBranch.matching("production/some-branch", protected_branches: [production, staging])).not_to include(staging)
+ end
+ end
+ end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index d8350000bf6..110df6bbd22 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -4,19 +4,20 @@ describe Repository, models: true do
include RepoHelpers
TestBlob = Struct.new(:name)
- let(:repository) { create(:project).repository }
+ let(:project) { create(:project) }
+ let(:repository) { project.repository }
let(:user) { create(:user) }
let(:commit_options) do
author = repository.user_to_committer(user)
{ message: 'Test message', committer: author, author: author }
end
let(:merge_commit) do
- source_sha = repository.find_branch('feature').target
- merge_commit_id = repository.merge(user, source_sha, 'master', commit_options)
+ merge_request = create(:merge_request, source_branch: 'feature', target_branch: 'master', source_project: project)
+ merge_commit_id = repository.merge(user, merge_request, commit_options)
repository.commit(merge_commit_id)
end
- describe :branch_names_contains do
+ describe '#branch_names_contains' do
subject { repository.branch_names_contains(sample_commit.id) }
it { is_expected.to include('master') }
@@ -24,7 +25,7 @@ describe Repository, models: true do
it { is_expected.not_to include('fix') }
end
- describe :tag_names_contains do
+ describe '#tag_names_contains' do
subject { repository.tag_names_contains(sample_commit.id) }
it { is_expected.to include('v1.1.0') }
@@ -72,13 +73,13 @@ describe Repository, models: true do
end
end
- describe :last_commit_for_path do
+ describe '#last_commit_for_path' do
subject { repository.last_commit_for_path(sample_commit.id, '.gitignore').id }
it { is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8') }
end
- describe :find_commits_by_message do
+ describe '#find_commits_by_message' do
subject { repository.find_commits_by_message('submodule').map{ |k| k.id } }
it { is_expected.to include('5937ac0a7beb003549fc5fd26fc247adbce4a52e') }
@@ -87,7 +88,7 @@ describe Repository, models: true do
it { is_expected.not_to include('913c66a37b4a45b9769037c55c2d238bd0942d2e') }
end
- describe :blob_at do
+ describe '#blob_at' do
context 'blank sha' do
subject { repository.blob_at(Gitlab::Git::BLANK_SHA, '.gitignore') }
@@ -95,7 +96,7 @@ describe Repository, models: true do
end
end
- describe :merged_to_root_ref? do
+ describe '#merged_to_root_ref?' do
context 'merged branch' do
subject { repository.merged_to_root_ref?('improve/awesome') }
@@ -103,7 +104,7 @@ describe Repository, models: true do
end
end
- describe :can_be_merged? do
+ describe '#can_be_merged?' do
context 'mergeable branches' do
subject { repository.can_be_merged?('0b4bc9a49b562e85de7cc9e834518ea6828729b9', 'master') }
@@ -129,6 +130,36 @@ describe Repository, models: true do
end
end
+ describe :commit_file do
+ it 'commits change to a file successfully' do
+ expect do
+ repository.commit_file(user, 'CHANGELOG', 'Changelog!',
+ 'Updates file content',
+ 'master', true)
+ end.to change { repository.commits('master').count }.by(1)
+
+ blob = repository.blob_at('master', 'CHANGELOG')
+
+ expect(blob.data).to eq('Changelog!')
+ end
+ end
+
+ describe :update_file do
+ it 'updates filename successfully' do
+ expect do
+ repository.update_file(user, 'NEWLICENSE', 'Copyright!',
+ branch: 'master',
+ previous_path: 'LICENSE',
+ message: 'Changes filename')
+ end.to change { repository.commits('master').count }.by(1)
+
+ files = repository.ls_files('master')
+
+ expect(files).not_to include('LICENSE')
+ expect(files).to include('NEWLICENSE')
+ end
+ end
+
describe "search_files" do
let(:results) { repository.search_files('feature', 'master') }
subject { results }
@@ -305,17 +336,17 @@ describe Repository, models: true do
end
end
- describe :add_branch do
+ describe '#add_branch' do
context 'when pre hooks were successful' do
it 'should run without errors' do
- hook = double(trigger: true)
+ 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 +362,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 +370,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')
@@ -349,16 +380,16 @@ describe Repository, models: true do
end
end
- describe :rm_branch do
+ describe '#rm_branch' do
context 'when pre hooks were successful' do
it 'should run without errors' do
- allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return(true)
+ 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 +399,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 +407,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')
@@ -386,7 +417,7 @@ describe Repository, models: true do
end
end
- describe :commit_with_hooks do
+ describe '#commit_with_hooks' do
context 'when pre hooks were successful' do
before do
expect_any_instance_of(GitHooksService).to receive(:execute).
@@ -408,7 +439,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 +562,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 +886,6 @@ describe Repository, models: true do
repository.after_create
end
-
end
describe "#copy_gitattributes" do
@@ -1055,12 +1083,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 +1098,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 +1148,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..67b3783d514 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 }
@@ -23,11 +22,11 @@ describe Service, models: true do
@testable = @service.can_test?
end
- describe :can_test do
+ describe '#can_test?' do
it { expect(@testable).to eq(true) }
end
- describe :test do
+ describe '#test' do
let(:data) { 'test' }
it 'test runs execute' do
@@ -46,7 +45,7 @@ describe Service, models: true do
@testable = @service.can_test?
end
- describe :can_test do
+ describe '#can_test?' do
it { expect(@testable).to eq(true) }
end
end
@@ -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..3bf82cf2668 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -31,6 +31,28 @@ describe User, models: true do
it { is_expected.to have_many(:spam_logs).dependent(:destroy) }
it { is_expected.to have_many(:todos).dependent(:destroy) }
it { is_expected.to have_many(:award_emoji).dependent(:destroy) }
+ it { is_expected.to have_many(:builds).dependent(:nullify) }
+ it { is_expected.to have_many(:pipelines).dependent(:nullify) }
+
+ describe '#group_members' do
+ it 'does not include group memberships for which user is a requester' do
+ 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
@@ -407,7 +429,7 @@ describe User, models: true do
end
end
- describe :not_in_project do
+ describe '.not_in_project' do
before do
User.delete_all
@user = create :user
@@ -426,6 +448,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 +471,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
@@ -557,7 +600,7 @@ describe User, models: true do
end
end
- describe :avatar_type do
+ describe '#avatar_type' do
let(:user) { create(:user) }
it "should be true if avatar is image" do
@@ -571,7 +614,7 @@ describe User, models: true do
end
end
- describe :requires_ldap_check? do
+ describe '#requires_ldap_check?' do
let(:user) { User.new }
it 'is false when LDAP is disabled' do
@@ -610,7 +653,7 @@ describe User, models: true do
end
context 'ldap synchronized user' do
- describe :ldap_user? do
+ describe '#ldap_user?' do
it 'is true if provider name starts with ldap' do
user = create(:omniauth_user, provider: 'ldapmain')
expect(user.ldap_user?).to be_truthy
@@ -627,7 +670,7 @@ describe User, models: true do
end
end
- describe :ldap_identity do
+ describe '#ldap_identity' do
it 'returns ldap identity' do
user = create :omniauth_user
expect(user.ldap_identity.provider).not_to be_empty
@@ -784,7 +827,7 @@ describe User, models: true do
end
end
- describe :can_be_removed? do
+ describe '#can_be_removed?' do
subject { create(:user) }
context 'no owned groups' do
@@ -844,16 +887,25 @@ describe User, models: true do
end
describe '#authorized_projects' do
- let!(:user) { create(:user) }
- let!(:private_project) { create(:project, :private) }
+ context 'with a minimum access level' do
+ it 'includes projects for which the user is an owner' do
+ user = create(:user)
+ project = create(:empty_project, :private, namespace: user.namespace)
- before do
- private_project.team << [user, Gitlab::Access::MASTER]
- end
+ expect(user.authorized_projects(Gitlab::Access::REPORTER))
+ .to contain_exactly(project)
+ end
- subject { user.authorized_projects }
+ it 'includes projects for which the user is a master' do
+ user = create(:user)
+ project = create(:empty_project, :private)
- it { is_expected.to eq([private_project]) }
+ project.team << [user, Gitlab::Access::MASTER]
+
+ expect(user.authorized_projects(Gitlab::Access::REPORTER))
+ .to contain_exactly(project)
+ end
+ end
end
describe '#ci_authorized_runners' do
diff --git a/spec/requests/api/api_helpers_spec.rb b/spec/requests/api/api_helpers_spec.rb
index f22db61e744..831889afb6c 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
@@ -50,7 +49,7 @@ describe API::Helpers, api: true do
it "should return nil for a user without access" do
env[API::Helpers::PRIVATE_TOKEN_HEADER] = user.private_token
- allow(Gitlab::UserAccess).to receive(:allowed?).and_return(false)
+ allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(false)
expect(current_user).to be_nil
end
@@ -74,7 +73,7 @@ describe API::Helpers, api: true do
it "should return nil for a user without access" do
env[API::Helpers::PRIVATE_TOKEN_HEADER] = personal_access_token.token
- allow(Gitlab::UserAccess).to receive(:allowed?).and_return(false)
+ allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(false)
expect(current_user).to be_nil
end
@@ -212,4 +211,27 @@ describe API::Helpers, api: true do
expect(sudo_identifier).to eq(' 123')
end
end
+
+ describe '.to_boolean' do
+ it 'converts a valid string to a boolean' do
+ expect(to_boolean('true')).to be_truthy
+ expect(to_boolean('YeS')).to be_truthy
+ expect(to_boolean('t')).to be_truthy
+ expect(to_boolean('1')).to be_truthy
+ expect(to_boolean('ON')).to be_truthy
+ expect(to_boolean('FaLse')).to be_falsy
+ expect(to_boolean('F')).to be_falsy
+ expect(to_boolean('NO')).to be_falsy
+ expect(to_boolean('n')).to be_falsy
+ expect(to_boolean('0')).to be_falsy
+ expect(to_boolean('oFF')).to be_falsy
+ end
+
+ it 'converts an invalid string to nil' do
+ expect(to_boolean('fals')).to be_nil
+ expect(to_boolean('yeah')).to be_nil
+ expect(to_boolean('')).to be_nil
+ expect(to_boolean(nil)).to be_nil
+ end
+ end
end
diff --git a/spec/requests/api/award_emoji_spec.rb b/spec/requests/api/award_emoji_spec.rb
index 2e65e7f1920..2b74dd4bbb0 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,29 @@ 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
+
+ it "normalizes +1 as thumbsup award" do
+ post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: '+1'
+
+ expect(issue.award_emoji.last.name).to eq("thumbsup")
+ end
+
+ context 'when the emoji already has been awarded' do
+ it 'returns a 404 status code' do
+ post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: 'thumbsup'
+ post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: 'thumbsup'
+
+ expect(response).to have_http_status(404)
+ expect(json_response["message"]).to match("has already been taken")
+ end
end
end
end
@@ -145,9 +160,25 @@ 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
+
+ it "normalizes +1 as thumbsup award" do
+ post api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: '+1'
+
+ expect(note.award_emoji.last.name).to eq("thumbsup")
+ end
+
+ context 'when the emoji already has been awarded' do
+ it 'returns a 404 status code' do
+ post api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: 'rocket'
+ post api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: 'rocket'
+
+ expect(response).to have_http_status(404)
+ expect(json_response["message"]).to match("has already been taken")
+ end
+ end
end
describe 'DELETE /projects/:id/awardable/:awardable_id/award_emoji/:award_id' do
@@ -157,13 +188,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 +204,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 +223,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..719da27f919 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,55 +27,138 @@ 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)
expect(json_response['protected']).to eq(false)
+ expect(json_response['developers_can_push']).to eq(false)
+ expect(json_response['developers_can_merge']).to eq(false)
end
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
+ describe 'PUT /projects/:id/repository/branches/:branch/protect' do
+ it 'protects 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)
expect(json_response['protected']).to eq(true)
+ expect(json_response['developers_can_push']).to eq(false)
+ expect(json_response['developers_can_merge']).to eq(false)
+ end
+
+ it 'protects a single branch and developers can push' do
+ put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user),
+ developers_can_push: true
+
+ expect(response).to have_http_status(200)
+ expect(json_response['name']).to eq(branch_name)
+ expect(json_response['commit']['id']).to eq(branch_sha)
+ expect(json_response['protected']).to eq(true)
+ expect(json_response['developers_can_push']).to eq(true)
+ expect(json_response['developers_can_merge']).to eq(false)
+ end
+
+ it 'protects a single branch and developers can merge' do
+ put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user),
+ developers_can_merge: true
+
+ expect(response).to have_http_status(200)
+ expect(json_response['name']).to eq(branch_name)
+ expect(json_response['commit']['id']).to eq(branch_sha)
+ expect(json_response['protected']).to eq(true)
+ expect(json_response['developers_can_push']).to eq(false)
+ expect(json_response['developers_can_merge']).to eq(true)
+ end
+
+ it 'protects a single branch and developers can push and merge' do
+ put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user),
+ developers_can_push: true, developers_can_merge: true
+
+ expect(response).to have_http_status(200)
+ expect(json_response['name']).to eq(branch_name)
+ expect(json_response['commit']['id']).to eq(branch_sha)
+ expect(json_response['protected']).to eq(true)
+ expect(json_response['developers_can_push']).to eq(true)
+ expect(json_response['developers_can_merge']).to eq(true)
+ end
+
+ it 'protects a single branch and developers cannot push and merge' do
+ put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user),
+ developers_can_push: 'tru', developers_can_merge: 'tr'
+
+ expect(response).to have_http_status(200)
+ expect(json_response['name']).to eq(branch_name)
+ expect(json_response['commit']['id']).to eq(branch_sha)
+ expect(json_response['protected']).to eq(true)
+ expect(json_response['developers_can_push']).to eq(false)
+ expect(json_response['developers_can_merge']).to eq(false)
+ end
+
+ context 'on a protected branch' do
+ let(:protected_branch) { 'foo' }
+
+ before do
+ project.repository.add_branch(user, protected_branch, 'master')
+ create(:protected_branch, project: project, name: protected_branch, developers_can_push: true, developers_can_merge: true)
+ end
+
+ it 'updates that a developer can push' do
+ put api("/projects/#{project.id}/repository/branches/#{protected_branch}/protect", user),
+ developers_can_push: false, developers_can_merge: false
+
+ expect(response).to have_http_status(200)
+ expect(json_response['name']).to eq(protected_branch)
+ expect(json_response['protected']).to eq(true)
+ expect(json_response['developers_can_push']).to eq(false)
+ expect(json_response['developers_can_merge']).to eq(false)
+ end
+
+ it 'does not update that a developer can push' do
+ put api("/projects/#{project.id}/repository/branches/#{protected_branch}/protect", user),
+ developers_can_push: 'foobar', developers_can_merge: 'foo'
+
+ expect(response).to have_http_status(200)
+ expect(json_response['name']).to eq(protected_branch)
+ expect(json_response['protected']).to eq(true)
+ expect(json_response['developers_can_push']).to eq(true)
+ expect(json_response['developers_can_merge']).to eq(true)
+ end
end
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 +167,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 +183,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 +193,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 +208,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 +221,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 +233,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 ac85f340922..f5b39c3d698 100644
--- a/spec/requests/api/builds_spec.rb
+++ b/spec/requests/api/builds_spec.rb
@@ -9,8 +9,8 @@ describe API::API, api: true do
let!(:project) { create(:project, creator_id: user.id) }
let!(:developer) { create(:project_member, :developer, user: user, project: project) }
let!(:reporter) { create(:project_member, :reporter, user: user2, project: project) }
- let(:pipeline) { create(:ci_pipeline, project: project)}
- let(:build) { create(:ci_build, pipeline: pipeline) }
+ let!(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit.id) }
+ let!(:build) { create(:ci_build, pipeline: pipeline) }
describe 'GET /projects/:id/builds ' do
let(:query) { '' }
@@ -19,15 +19,20 @@ 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
+ it 'returns correct values' do
+ expect(json_response).not_to be_empty
+ expect(json_response.first['commit']['id']).to eq project.commit.id
+ end
+
context 'filter project with one scope element' 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
@@ -36,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
@@ -44,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
@@ -52,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
@@ -84,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
@@ -93,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
@@ -111,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
@@ -120,24 +162,24 @@ 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
describe 'GET /projects/:id/builds/:build_id/trace' do
let(:build) { create(:ci_build, :trace, pipeline: pipeline) }
-
+
before { get api("/projects/#{project.id}/builds/#{build.id}/trace", api_user) }
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
@@ -146,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
@@ -157,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
@@ -166,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
@@ -175,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
@@ -188,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
@@ -198,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
@@ -207,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/gitignores_spec.rb b/spec/requests/api/gitignores_spec.rb
deleted file mode 100644
index aab2d8c81b9..00000000000
--- a/spec/requests/api/gitignores_spec.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-require 'spec_helper'
-
-describe API::Gitignores, api: true do
- include ApiHelpers
-
- describe 'Entity Gitignore' do
- before { get api('/gitignores/Ruby') }
-
- it { expect(json_response['name']).to eq('Ruby') }
- it { expect(json_response['content']).to include('*.gem') }
- end
-
- describe 'Entity GitignoresList' do
- before { get api('/gitignores') }
-
- it { expect(json_response.first['name']).not_to be_nil }
- it { expect(json_response.first['content']).to be_nil }
- end
-
- describe 'GET /gitignores' do
- it 'returns a list of available license templates' do
- get api('/gitignores')
-
- expect(response.status).to eq(200)
- expect(json_response).to be_an Array
- expect(json_response.size).to be > 15
- end
- 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..f6f85d6e95e 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
@@ -56,13 +56,21 @@ describe API::API, api: true do
context "git push with project.wiki" do
it 'responds with success' do
- project_wiki = create(:project, name: 'my.wiki', path: 'my.wiki')
- project_wiki.team << [user, :developer]
+ push(key, project.wiki)
+
+ expect(response).to have_http_status(200)
+ expect(json_response["status"]).to be_truthy
+ expect(json_response["repository_path"]).to eq(project.wiki.repository.path_to_repo)
+ end
+ end
- push(key, project_wiki)
+ context "git pull with project.wiki" do
+ it 'responds with success' do
+ pull(key, project.wiki)
- 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.wiki.repository.path_to_repo)
end
end
@@ -70,8 +78,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 +88,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 +104,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 +113,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 +130,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 +139,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 +157,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 +166,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 +183,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 +192,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 +202,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 +211,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 +305,8 @@ describe API::API, api: true do
key_id: key.id,
project: project.path_with_namespace,
action: 'git-upload-archive',
- secret_token: secret_token
+ secret_token: secret_token,
+ protocol: 'ssh'
)
end
end
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 59e557c5b2a..12f2cfa6942 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,20 +129,162 @@ 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
end
end
+ describe "GET /groups/:id/issues" do
+ let!(:group) { create(:group) }
+ let!(:group_project) { create(:project, :public, creator_id: user.id, namespace: group) }
+ let!(:group_closed_issue) do
+ create :closed_issue,
+ author: user,
+ assignee: user,
+ project: group_project,
+ state: :closed,
+ milestone: group_milestone
+ end
+ let!(:group_confidential_issue) do
+ create :issue,
+ :confidential,
+ project: group_project,
+ author: author,
+ assignee: assignee
+ end
+ let!(:group_issue) do
+ create :issue,
+ author: user,
+ assignee: user,
+ project: group_project,
+ milestone: group_milestone
+ end
+ let!(:group_label) do
+ create(:label, title: 'group_lbl', color: '#FFAABB', project: group_project)
+ end
+ let!(:group_label_link) { create(:label_link, label: group_label, target: group_issue) }
+ let!(:group_milestone) { create(:milestone, title: '3.0.0', project: group_project) }
+ let!(:group_empty_milestone) do
+ create(:milestone, title: '4.0.0', project: group_project)
+ end
+ let!(:group_note) { create(:note_on_issue, author: user, project: group_project, noteable: group_issue) }
+
+ before do
+ group_project.team << [user, :reporter]
+ end
+ let(:base_url) { "/groups/#{group.id}/issues" }
+
+ it 'returns group issues without confidential issues for non project members' do
+ get api(base_url, non_member)
+
+ 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)
+ end
+
+ it 'returns group confidential issues for author' do
+ get api(base_url, author)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(2)
+ end
+
+ it 'returns group confidential issues for assignee' do
+ get api(base_url, assignee)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(2)
+ end
+
+ it 'returns group issues with confidential issues for project members' do
+ get api(base_url, user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(2)
+ end
+
+ it 'returns group confidential issues for admin' do
+ get api(base_url, admin)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(2)
+ end
+
+ it 'returns an array of labeled group issues' do
+ get api("#{base_url}?labels=#{group_label.title}", user)
+
+ 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])
+ end
+
+ 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).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(0)
+ end
+
+ it 'returns an empty array if no group issue matches labels' do
+ get api("#{base_url}?labels=foo,bar", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(0)
+ end
+
+ it 'returns an empty array if no issue matches milestone' do
+ get api("#{base_url}?milestone=#{group_empty_milestone.title}", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(0)
+ end
+
+ it 'returns an empty array if milestone does not exist' do
+ get api("#{base_url}?milestone=foo", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(0)
+ end
+
+ it 'returns an array of issues in given milestone' do
+ get api("#{base_url}?milestone=#{group_milestone.title}", user)
+
+ 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)
+ end
+
+ it 'returns an array of issues matching state in milestone' do
+ get api("#{base_url}?milestone=#{group_milestone.title}"\
+ '&state=closed', user)
+
+ 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)
+ end
+ end
+
describe "GET /projects/:id/issues" do
let(:base_url) { "/projects/#{project.id}" }
let(:title) { milestone.title }
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)
@@ -150,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)
@@ -158,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)
@@ -166,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)
@@ -174,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)
@@ -182,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)
@@ -190,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])
@@ -198,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])
@@ -206,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)
@@ -237,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)
@@ -248,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)
@@ -266,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
@@ -281,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
@@ -329,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'])
@@ -337,33 +479,51 @@ 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)'
])
end
+ context 'with due date' do
+ it 'creates a new project issue' do
+ due_date = 2.weeks.from_now.strftime('%Y-%m-%d')
+
+ post api("/projects/#{project.id}/issues", user),
+ title: 'new issue', due_date: due_date
+
+ 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['due_date']).to eq(due_date)
+ end
+ end
+
context 'when an admin or owner makes the request' do
it 'accepts the creation date to be set' do
creation_time = 2.weeks.ago
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
@@ -387,7 +547,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
@@ -404,7 +564,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
@@ -412,48 +572,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
@@ -466,46 +631,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)'
])
@@ -516,7 +678,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"
@@ -527,7 +689,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)
@@ -535,15 +697,26 @@ describe API::API, api: true do
end
end
+ describe 'PUT /projects/:id/issues/:issue_id to update due date' do
+ it 'creates a new project issue' do
+ due_date = 2.weeks.from_now.strftime('%Y-%m-%d')
+
+ put api("/projects/#{project.id}/issues/#{issue.id}", user), due_date: due_date
+
+ expect(response).to have_http_status(200)
+ expect(json_response['due_date']).to eq(due_date)
+ end
+ end
+
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
@@ -552,7 +725,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
@@ -566,7 +739,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
@@ -575,7 +748,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
@@ -585,7 +758,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
@@ -594,7 +767,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
@@ -603,7 +776,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
@@ -612,7 +785,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
@@ -621,7 +794,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
@@ -630,26 +803,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
@@ -657,26 +830,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..651b91e9f68 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)
@@ -138,15 +138,19 @@ describe API::API, api: true do
expect(json_response['work_in_progress']).to be_falsy
expect(json_response['merge_when_build_succeeds']).to be_falsy
expect(json_response['merge_status']).to eq('can_be_merged')
+ expect(json_response['should_close_merge_request']).to be_falsy
+ expect(json_response['force_close_merge_request']).to be_falsy
end
it "should return merge_request" do
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)
expect(json_response['merge_status']).to eq('can_be_merged')
+ expect(json_response['should_close_merge_request']).to be_falsy
+ expect(json_response['force_close_merge_request']).to be_falsy
end
it 'should return merge_request by iid' do
@@ -159,7 +163,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 +171,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 +190,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 +203,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 +217,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 +226,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 +280,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 +298,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 +309,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 +339,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 +349,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 +371,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 +379,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 +387,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 +398,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 +407,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 +430,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 +438,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 +461,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 +470,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 +517,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 +526,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 +549,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 +561,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 +569,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 +582,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 +594,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 +615,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..152cd802839 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)
@@ -81,10 +81,22 @@ describe API::API, api: true do
expect(json_response.first.keys).not_to include('open_issues_count')
end
+ context 'GET /projects?simple=true' do
+ it 'returns a simplified version of all the projects' do
+ expected_keys = ["id", "http_url_to_repo", "web_url", "name", "name_with_namespace", "path", "path_with_namespace"]
+
+ get api('/projects?simple=true', user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first.keys).to match_array expected_keys
+ end
+ end
+
context 'and using search' do
it 'should return searched project' do
get api('/projects', user), { search: project.name }
- 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 +105,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 +133,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 +147,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 +185,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 +197,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 +229,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 +285,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 +307,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 +337,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 +392,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 +404,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 +470,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 +481,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 +493,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 +508,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 +527,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 +548,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 +564,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 +573,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 +588,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 +602,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 +610,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 +622,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 +652,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 +661,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 +684,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 +710,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 +724,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 +739,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 +747,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 +755,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 +770,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 +779,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 +788,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 +846,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 +863,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 +885,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 +893,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 +902,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 +913,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 +923,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 +941,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 +955,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 +964,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 +990,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 +1000,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 +1013,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 +1026,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 +1036,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 +1049,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 +1062,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 +1072,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 +1086,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 +1101,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 +1110,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 +1119,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 73ae8ef631c..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
@@ -187,14 +187,16 @@ describe API::Runners, api: true do
update_runner(shared_runner.id, admin, description: "#{description}_updated",
active: !active,
tag_list: ['ruby2.1', 'pgsql', 'mysql'],
- run_untagged: 'false')
+ run_untagged: 'false',
+ 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')
- expect(shared_runner.run_untagged?).to be false
+ expect(shared_runner.run_untagged?).to be(false)
+ expect(shared_runner.locked?).to be(true)
end
end
@@ -204,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
@@ -213,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)
@@ -226,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
@@ -234,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
@@ -242,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
@@ -253,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
@@ -265,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
@@ -274,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
@@ -296,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
@@ -324,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
@@ -335,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
@@ -345,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
@@ -353,35 +355,47 @@ 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
describe 'POST /projects/:id/runners' do
context 'authorized user' do
- it 'should enable specific runner' do
- specific_runner2 = create(:ci_runner).tap do |runner|
+ let(:specific_runner2) do
+ create(:ci_runner).tap do |runner|
create(:ci_runner_project, runner: runner, project: project2)
end
+ end
+ it 'should enable specific runner' 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(201)
+ expect(response).to have_http_status(409)
+ end
+
+ it 'should not enable locked runner' do
+ specific_runner2.update(locked: true)
+
+ expect do
+ post api("/projects/#{project.id}/runners", user), runner_id: specific_runner2.id
+ end.to change{ project.runners.count }.by(0)
+
+ 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
@@ -389,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
@@ -397,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
@@ -412,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
@@ -420,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
@@ -432,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
@@ -441,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
@@ -456,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
@@ -464,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
new file mode 100644
index 00000000000..68d0f41b489
--- /dev/null
+++ b/spec/requests/api/templates_spec.rb
@@ -0,0 +1,52 @@
+require 'spec_helper'
+
+describe API::Templates, api: true do
+ include ApiHelpers
+
+ describe 'the Template Entity' do
+ before { get api('/gitignores/Ruby') }
+
+ it { expect(json_response['name']).to eq('Ruby') }
+ it { expect(json_response['content']).to include('*.gem') }
+ end
+
+ describe 'the TemplateList Entity' do
+ before { get api('/gitignores') }
+
+ it { expect(json_response.first['name']).not_to be_nil }
+ it { expect(json_response.first['content']).to be_nil }
+ end
+
+ context 'requesting gitignores' do
+ describe 'GET /gitignores' do
+ it 'returns a list of available gitignore templates' do
+ get api('/gitignores')
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to be > 15
+ end
+ end
+ end
+
+ context 'requesting gitlab-ci-ymls' do
+ describe 'GET /gitlab_ci_ymls' do
+ it 'returns a list of available gitlab_ci_ymls' do
+ get api('/gitlab_ci_ymls')
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['name']).not_to be_nil
+ end
+ end
+ end
+
+ describe 'GET /gitlab_ci_ymls/Ruby' do
+ it 'adds a disclaimer on the top' do
+ get api('/gitlab_ci_ymls/Ruby')
+
+ expect(response).to have_http_status(200)
+ expect(json_response['content']).to start_with("# This file is a template,")
+ end
+ end
+end
diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb
new file mode 100644
index 00000000000..3ccd0af652f
--- /dev/null
+++ b/spec/requests/api/todos_spec.rb
@@ -0,0 +1,189 @@
+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(response.body).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/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb
new file mode 100644
index 00000000000..93d2bc160cc
--- /dev/null
+++ b/spec/requests/lfs_http_spec.rb
@@ -0,0 +1,768 @@
+require 'spec_helper'
+
+describe Gitlab::Lfs::Router do
+ let(:user) { create(:user) }
+ let!(:lfs_object) { create(:lfs_object, :with_file) }
+
+ let(:headers) do
+ {
+ 'Authorization' => authorization,
+ 'X-Sendfile-Type' => sendfile
+ }.compact
+ end
+ let(:authorization) { }
+ let(:sendfile) { }
+
+ let(:sample_oid) { lfs_object.oid }
+ let(:sample_size) { lfs_object.size }
+
+ describe 'when lfs is disabled' do
+ let(:project) { create(:empty_project) }
+ let(:body) do
+ {
+ 'objects' => [
+ { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
+ 'size' => 1575078
+ },
+ { 'oid' => sample_oid,
+ 'size' => sample_size
+ }
+ ],
+ 'operation' => 'upload'
+ }
+ end
+
+ before do
+ allow(Gitlab.config.lfs).to receive(:enabled).and_return(false)
+ post_json "#{project.http_url_to_repo}/info/lfs/objects/batch", body, headers
+ end
+
+ it 'responds with 501' do
+ expect(response).to have_http_status(501)
+ expect(json_response).to include('message' => 'Git LFS is not enabled on this GitLab server, contact your admin.')
+ end
+ end
+
+ describe 'deprecated API' do
+ let(:project) { create(:empty_project) }
+
+ before do
+ enable_lfs
+ end
+
+ shared_examples 'a deprecated' do
+ it 'responds with 501' do
+ expect(response).to have_http_status(501)
+ end
+
+ it 'returns deprecated message' do
+ expect(json_response).to include('message' => 'Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.')
+ end
+ end
+
+ context 'when fetching lfs object using deprecated API' do
+ let(:authorization) { authorize_user }
+
+ before do
+ get "#{project.http_url_to_repo}/info/lfs/objects/#{sample_oid}", nil, headers
+ end
+
+ it_behaves_like 'a deprecated'
+ end
+
+ context 'when handling lfs request using deprecated API' do
+ before do
+ post_json "#{project.http_url_to_repo}/info/lfs/objects", nil, headers
+ end
+
+ it_behaves_like 'a deprecated'
+ end
+ end
+
+ describe 'when fetching lfs object' do
+ let(:project) { create(:empty_project) }
+ let(:update_permissions) { }
+
+ before do
+ enable_lfs
+ update_permissions
+ get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", nil, headers
+ end
+
+ context 'and request comes from gitlab-workhorse' do
+ context 'without user being authorized' do
+ it 'responds with status 401' do
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context 'with required headers' do
+ shared_examples 'responds with a file' do
+ let(:sendfile) { 'X-Sendfile' }
+
+ it 'responds with status 200' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'responds with the file location' do
+ expect(response.headers['Content-Type']).to eq('application/octet-stream')
+ expect(response.headers['X-Sendfile']).to eq(lfs_object.file.path)
+ end
+ end
+
+ context 'with user is authorized' do
+ let(:authorization) { authorize_user }
+
+ context 'and does not have project access' do
+ let(:update_permissions) do
+ project.lfs_objects << lfs_object
+ end
+
+ it 'responds with status 403' do
+ expect(response).to have_http_status(403)
+ end
+ end
+
+ context 'and does have project access' do
+ let(:update_permissions) do
+ project.team << [user, :master]
+ project.lfs_objects << lfs_object
+ end
+
+ it_behaves_like 'responds with a file'
+ end
+ end
+
+ context 'when CI is authorized' do
+ let(:authorization) { authorize_ci_project }
+
+ let(:update_permissions) do
+ project.lfs_objects << lfs_object
+ end
+
+ it_behaves_like 'responds with a file'
+ end
+ end
+
+ context 'without required headers' do
+ let(:authorization) { authorize_user }
+
+ it 'responds with status 403' do
+ expect(response).to have_http_status(403)
+ end
+ end
+ end
+ end
+
+ describe 'when handling lfs batch request' do
+ let(:update_lfs_permissions) { }
+ let(:update_user_permissions) { }
+
+ before do
+ enable_lfs
+ update_lfs_permissions
+ update_user_permissions
+ post_json "#{project.http_url_to_repo}/info/lfs/objects/batch", body, headers
+ end
+
+ describe 'download' do
+ let(:project) { create(:empty_project) }
+ let(:body) do
+ { 'operation' => 'download',
+ 'objects' => [
+ { 'oid' => sample_oid,
+ 'size' => sample_size
+ }]
+ }
+ end
+
+ shared_examples 'an authorized requests' do
+ context 'when downloading an lfs object that is assigned to our project' do
+ let(:update_lfs_permissions) do
+ project.lfs_objects << lfs_object
+ end
+
+ it 'responds with status 200' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'with href to download' do
+ expect(json_response).to eq('objects' => [
+ { 'oid' => sample_oid,
+ 'size' => sample_size,
+ 'actions' => {
+ 'download' => {
+ 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
+ 'header' => { 'Authorization' => authorization }
+ }
+ }
+ }])
+ end
+ end
+
+ context 'when downloading an lfs object that is assigned to other project' do
+ let(:other_project) { create(:empty_project) }
+ let(:update_lfs_permissions) do
+ other_project.lfs_objects << lfs_object
+ end
+
+ it 'responds with status 200' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'with href to download' do
+ expect(json_response).to eq('objects' => [
+ { 'oid' => sample_oid,
+ 'size' => sample_size,
+ 'error' => {
+ 'code' => 404,
+ 'message' => "Object does not exist on the server or you don't have permissions to access it",
+ }
+ }])
+ end
+ end
+
+ context 'when downloading a lfs object that does not exist' do
+ let(:body) do
+ { 'operation' => 'download',
+ 'objects' => [
+ { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
+ 'size' => 1575078
+ }]
+ }
+ end
+
+ it 'responds with status 200' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'with an 404 for specific object' do
+ expect(json_response).to eq('objects' => [
+ { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
+ 'size' => 1575078,
+ 'error' => {
+ 'code' => 404,
+ 'message' => "Object does not exist on the server or you don't have permissions to access it",
+ }
+ }])
+ end
+ end
+
+ context 'when downloading one new and one existing lfs object' do
+ let(:body) do
+ { 'operation' => 'download',
+ 'objects' => [
+ { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
+ 'size' => 1575078
+ },
+ { 'oid' => sample_oid,
+ 'size' => sample_size
+ }
+ ]
+ }
+ end
+
+ let(:update_lfs_permissions) do
+ project.lfs_objects << lfs_object
+ end
+
+ it 'responds with status 200' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'responds with upload hypermedia link for the new object' do
+ expect(json_response).to eq('objects' => [
+ { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
+ 'size' => 1575078,
+ 'error' => {
+ 'code' => 404,
+ 'message' => "Object does not exist on the server or you don't have permissions to access it",
+ }
+ },
+ { 'oid' => sample_oid,
+ 'size' => sample_size,
+ 'actions' => {
+ 'download' => {
+ 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
+ 'header' => { 'Authorization' => authorization }
+ }
+ }
+ }])
+ end
+ end
+ end
+
+ context 'when user is authenticated' do
+ let(:authorization) { authorize_user }
+
+ let(:update_user_permissions) do
+ project.team << [user, role]
+ end
+
+ it_behaves_like 'an authorized requests' do
+ let(:role) { :reporter }
+ end
+
+ context 'when user does is not member of the project' do
+ let(:role) { :guest }
+
+ it 'responds with 403' do
+ expect(response).to have_http_status(403)
+ end
+ end
+
+ context 'when user does not have download access' do
+ let(:role) { :guest }
+
+ it 'responds with 403' do
+ expect(response).to have_http_status(403)
+ end
+ end
+ end
+
+ context 'when CI is authorized' do
+ let(:authorization) { authorize_ci_project }
+
+ it_behaves_like 'an authorized requests'
+ end
+
+ context 'when user is not authenticated' do
+ describe 'is accessing public project' do
+ let(:project) { create(:project, :public) }
+
+ let(:update_lfs_permissions) do
+ project.lfs_objects << lfs_object
+ end
+
+ it 'responds with status 200 and href to download' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'responds with status 200 and href to download' do
+ expect(json_response).to eq('objects' => [
+ { 'oid' => sample_oid,
+ 'size' => sample_size,
+ 'actions' => {
+ 'download' => {
+ 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
+ 'header' => {}
+ }
+ }
+ }])
+ end
+ end
+
+ describe 'is accessing non-public project' do
+ let(:update_lfs_permissions) do
+ project.lfs_objects << lfs_object
+ end
+
+ it 'responds with authorization required' do
+ expect(response).to have_http_status(401)
+ end
+ end
+ end
+ end
+
+ describe 'upload' do
+ let(:project) { create(:project, :public) }
+ let(:body) do
+ { 'operation' => 'upload',
+ 'objects' => [
+ { 'oid' => sample_oid,
+ 'size' => sample_size
+ }]
+ }
+ end
+
+ describe 'when request is authenticated' do
+ describe 'when user has project push access' do
+ let(:authorization) { authorize_user }
+
+ let(:update_user_permissions) do
+ project.team << [user, :developer]
+ end
+
+ context 'when pushing an lfs object that already exists' do
+ let(:other_project) { create(:empty_project) }
+ let(:update_lfs_permissions) do
+ other_project.lfs_objects << lfs_object
+ end
+
+ it 'responds with status 200' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'responds with links the object to the project' do
+ expect(json_response['objects']).to be_kind_of(Array)
+ expect(json_response['objects'].first['oid']).to eq(sample_oid)
+ expect(json_response['objects'].first['size']).to eq(sample_size)
+ expect(lfs_object.projects.pluck(:id)).not_to include(project.id)
+ expect(lfs_object.projects.pluck(:id)).to include(other_project.id)
+ expect(json_response['objects'].first['actions']['upload']['href']).to eq("#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}")
+ expect(json_response['objects'].first['actions']['upload']['header']).to eq('Authorization' => authorization)
+ end
+ end
+
+ context 'when pushing a lfs object that does not exist' do
+ let(:body) do
+ { 'operation' => 'upload',
+ 'objects' => [
+ { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
+ 'size' => 1575078
+ }]
+ }
+ end
+
+ it 'responds with status 200' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'responds with upload hypermedia link' do
+ expect(json_response['objects']).to be_kind_of(Array)
+ expect(json_response['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897")
+ expect(json_response['objects'].first['size']).to eq(1575078)
+ expect(json_response['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078")
+ expect(json_response['objects'].first['actions']['upload']['header']).to eq('Authorization' => authorization)
+ end
+ end
+
+ context 'when pushing one new and one existing lfs object' do
+ let(:body) do
+ { 'operation' => 'upload',
+ 'objects' => [
+ { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
+ 'size' => 1575078
+ },
+ { 'oid' => sample_oid,
+ 'size' => sample_size
+ }
+ ]
+ }
+ end
+
+ let(:update_lfs_permissions) do
+ project.lfs_objects << lfs_object
+ end
+
+ it 'responds with status 200' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'responds with upload hypermedia link for the new object' do
+ expect(json_response['objects']).to be_kind_of(Array)
+
+ expect(json_response['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897")
+ expect(json_response['objects'].first['size']).to eq(1575078)
+ expect(json_response['objects'].first['actions']['upload']['href']).to eq("#{project.http_url_to_repo}/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078")
+ expect(json_response['objects'].first['actions']['upload']['header']).to eq("Authorization" => authorization)
+
+ expect(json_response['objects'].last['oid']).to eq(sample_oid)
+ expect(json_response['objects'].last['size']).to eq(sample_size)
+ expect(json_response['objects'].last).not_to have_key('actions')
+ end
+ end
+ end
+
+ context 'when user does not have push access' do
+ let(:authorization) { authorize_user }
+
+ it 'responds with 403' do
+ expect(response).to have_http_status(403)
+ end
+ end
+
+ context 'when CI is authorized' do
+ let(:authorization) { authorize_ci_project }
+
+ it 'responds with 401' do
+ expect(response).to have_http_status(401)
+ end
+ end
+ end
+
+ context 'when user is not authenticated' do
+ context 'when user has push access' do
+ let(:update_user_permissions) do
+ project.team << [user, :master]
+ end
+
+ it 'responds with status 401' do
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context 'when user does not have push access' do
+ it 'responds with status 401' do
+ expect(response).to have_http_status(401)
+ end
+ end
+ end
+
+ context 'when CI is authorized' do
+ let(:authorization) { authorize_ci_project }
+
+ it 'responds with status 403' do
+ expect(response).to have_http_status(401)
+ end
+ end
+ end
+
+ describe 'unsupported' do
+ let(:project) { create(:empty_project) }
+ let(:body) do
+ { 'operation' => 'other',
+ 'objects' => [
+ { 'oid' => sample_oid,
+ 'size' => sample_size
+ }]
+ }
+ end
+
+ it 'responds with status 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ describe 'when pushing a lfs object' do
+ before do
+ enable_lfs
+ end
+
+ shared_examples 'unauthorized' do
+ context 'and request is sent by gitlab-workhorse to authorize the request' do
+ before do
+ put_authorize
+ end
+
+ it 'responds with status 401' do
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context 'and request is sent by gitlab-workhorse to finalize the upload' do
+ before do
+ put_finalize
+ end
+
+ it 'responds with status 401' do
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context 'and request is sent with a malformed headers' do
+ before do
+ put_finalize('cat /etc/passwd')
+ end
+
+ it 'does not recognize it as a valid lfs command' do
+ expect(response).to have_http_status(403)
+ end
+ end
+ end
+
+ shared_examples 'forbidden' do
+ context 'and request is sent by gitlab-workhorse to authorize the request' do
+ before do
+ put_authorize
+ end
+
+ it 'responds with 403' do
+ expect(response).to have_http_status(403)
+ end
+ end
+
+ context 'and request is sent by gitlab-workhorse to finalize the upload' do
+ before do
+ put_finalize
+ end
+
+ it 'responds with 403' do
+ expect(response).to have_http_status(403)
+ end
+ end
+ end
+
+ describe 'to one project' do
+ let(:project) { create(:empty_project) }
+
+ describe 'when user is authenticated' do
+ let(:authorization) { authorize_user }
+
+ describe 'when user has push access to the project' do
+ before do
+ project.team << [user, :developer]
+ end
+
+ context 'and request is sent by gitlab-workhorse to authorize the request' do
+ before do
+ put_authorize
+ end
+
+ it 'responds with status 200' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'responds with status 200, location of lfs store and object details' do
+ expect(json_response['StoreLFSPath']).to eq("#{Gitlab.config.shared.path}/lfs-objects/tmp/upload")
+ expect(json_response['LfsOid']).to eq(sample_oid)
+ expect(json_response['LfsSize']).to eq(sample_size)
+ end
+ end
+
+ context 'and request is sent by gitlab-workhorse to finalize the upload' do
+ before do
+ put_finalize
+ end
+
+ it 'responds with status 200' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'lfs object is linked to the project' do
+ expect(lfs_object.projects.pluck(:id)).to include(project.id)
+ end
+ end
+ end
+
+ describe 'and user does not have push access' do
+ it_behaves_like 'forbidden'
+ end
+ end
+
+ context 'when CI is authenticated' do
+ let(:authorization) { authorize_ci_project }
+
+ it_behaves_like 'unauthorized'
+ end
+
+ context 'for unauthenticated' do
+ it_behaves_like 'unauthorized'
+ end
+ end
+
+ describe 'to a forked project' do
+ let(:upstream_project) { create(:project, :public) }
+ let(:project_owner) { create(:user) }
+ let(:project) { fork_project(upstream_project, project_owner) }
+
+ describe 'when user is authenticated' do
+ let(:authorization) { authorize_user }
+
+ describe 'when user has push access to the project' do
+ before do
+ project.team << [user, :developer]
+ end
+
+ context 'and request is sent by gitlab-workhorse to authorize the request' do
+ before do
+ put_authorize
+ end
+
+ it 'responds with status 200' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'with location of lfs store and object details' do
+ expect(json_response['StoreLFSPath']).to eq("#{Gitlab.config.shared.path}/lfs-objects/tmp/upload")
+ expect(json_response['LfsOid']).to eq(sample_oid)
+ expect(json_response['LfsSize']).to eq(sample_size)
+ end
+ end
+
+ context 'and request is sent by gitlab-workhorse to finalize the upload' do
+ before do
+ put_finalize
+ end
+
+ it 'responds with status 200' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'lfs object is linked to the source project' do
+ expect(lfs_object.projects.pluck(:id)).to include(upstream_project.id)
+ end
+ end
+ end
+
+ describe 'and user does not have push access' do
+ it_behaves_like 'forbidden'
+ end
+ end
+
+ context 'when CI is authenticated' do
+ let(:authorization) { authorize_ci_project }
+
+ it_behaves_like 'unauthorized'
+ end
+
+ context 'for unauthenticated' do
+ it_behaves_like 'unauthorized'
+ end
+
+ describe 'and second project not related to fork or a source project' do
+ let(:second_project) { create(:empty_project) }
+ let(:authorization) { authorize_user }
+
+ before do
+ second_project.team << [user, :master]
+ upstream_project.lfs_objects << lfs_object
+ end
+
+ context 'when pushing the same lfs object to the second project' do
+ before do
+ put "#{second_project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}", nil,
+ headers.merge('X-Gitlab-Lfs-Tmp' => lfs_tmp_file).compact
+ end
+
+ it 'responds with status 200' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'links the lfs object to the project' do
+ expect(lfs_object.projects.pluck(:id)).to include(second_project.id, upstream_project.id)
+ end
+ end
+ end
+ end
+
+ def put_authorize
+ put "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}/authorize", nil, headers
+ end
+
+ def put_finalize(lfs_tmp = lfs_tmp_file)
+ put "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}", nil,
+ headers.merge('X-Gitlab-Lfs-Tmp' => lfs_tmp).compact
+ end
+
+ def lfs_tmp_file
+ "#{sample_oid}012345678"
+ end
+ end
+
+ def enable_lfs
+ allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
+ end
+
+ def authorize_ci_project
+ ActionController::HttpAuthentication::Basic.encode_credentials('gitlab-ci-token', project.runners_token)
+ end
+
+ def authorize_user
+ ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password)
+ end
+
+ def fork_project(project, user, object = nil)
+ allow(RepositoryForkWorker).to receive(:perform_async).and_return(true)
+ Projects::ForkService.new(project, user, {}).execute
+ end
+
+ def post_json(url, body = nil, headers = nil)
+ post(url, body.try(:to_json), (headers || {}).merge('Content-Type' => 'application/json'))
+ end
+
+ def json_response
+ @json_response ||= JSON.parse(response.body)
+ end
+end
diff --git a/spec/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..0a52c1ab933 100644
--- a/spec/routing/routing_spec.rb
+++ b/spec/routing/routing_spec.rb
@@ -98,7 +98,7 @@ describe SnippetsController, "routing" do
end
# help GET /help(.:format) help#index
-# help_page GET /help/:category/:file(.:format) help#show {:category=>/.*/, :file=>/[^\/\.]+/}
+# help_page GET /help/*path(.:format) help#show
# help_shortcuts GET /help/shortcuts(.:format) help#shortcuts
# help_ui GET /help/ui(.:format) help#ui
describe HelpController, "routing" do
@@ -109,23 +109,16 @@ describe HelpController, "routing" do
it 'to #show' do
path = '/help/markdown/markdown.md'
expect(get(path)).to route_to('help#show',
- category: 'markdown',
- file: 'markdown',
+ path: 'markdown/markdown',
format: 'md')
path = '/help/workflow/protected_branches/protected_branches1.png'
expect(get(path)).to route_to('help#show',
- category: 'workflow/protected_branches',
- file: 'protected_branches1',
+ path: 'workflow/protected_branches/protected_branches1',
format: 'png')
- end
-
- it 'to #shortcuts' do
- expect(get('/help/shortcuts')).to route_to('help#shortcuts')
- end
-
- it 'to #ui' do
- expect(get('/help/ui')).to route_to('help#ui')
+
+ path = '/help/ui'
+ expect(get(path)).to route_to('help#ui')
end
end
@@ -253,7 +246,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/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb
index 67777ad48bc..7cc71f706ce 100644
--- a/spec/services/auth/container_registry_authentication_service_spec.rb
+++ b/spec/services/auth/container_registry_authentication_service_spec.rb
@@ -87,51 +87,105 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do
end
context 'user authorization' do
- let(:project) { create(:project) }
let(:current_user) { create(:user) }
- context 'allow to use scope-less authentication' do
- it_behaves_like 'a valid token'
- end
+ context 'for private project' do
+ let(:project) { create(:empty_project) }
- context 'allow developer to push images' do
- before { project.team << [current_user, :developer] }
+ context 'allow to use scope-less authentication' do
+ it_behaves_like 'a valid token'
+ end
- let(:current_params) do
- { scope: "repository:#{project.path_with_namespace}:push" }
+ context 'allow developer to push images' do
+ before { project.team << [current_user, :developer] }
+
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:push" }
+ end
+
+ it_behaves_like 'a pushable'
end
- it_behaves_like 'a pushable'
- end
+ context 'allow reporter to pull images' do
+ before { project.team << [current_user, :reporter] }
+
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:pull" }
+ end
- context 'allow reporter to pull images' do
- before { project.team << [current_user, :reporter] }
+ it_behaves_like 'a pullable'
+ end
- let(:current_params) do
- { scope: "repository:#{project.path_with_namespace}:pull" }
+ context 'return a least of privileges' do
+ before { project.team << [current_user, :reporter] }
+
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:push,pull" }
+ end
+
+ it_behaves_like 'a pullable'
end
- it_behaves_like 'a pullable'
+ context 'disallow guest to pull or push images' do
+ before { project.team << [current_user, :guest] }
+
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:pull,push" }
+ end
+
+ it_behaves_like 'an inaccessible'
+ end
end
- context 'return a least of privileges' do
- before { project.team << [current_user, :reporter] }
+ context 'for public project' do
+ let(:project) { create(:empty_project, :public) }
- let(:current_params) do
- { scope: "repository:#{project.path_with_namespace}:push,pull" }
+ context 'allow anyone to pull images' do
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:pull" }
+ end
+
+ it_behaves_like 'a pullable'
end
- it_behaves_like 'a pullable'
+ context 'disallow anyone to push images' do
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:push" }
+ end
+
+ it_behaves_like 'an inaccessible'
+ end
end
- context 'disallow guest to pull or push images' do
- before { project.team << [current_user, :guest] }
+ context 'for internal project' do
+ let(:project) { create(:empty_project, :internal) }
- let(:current_params) do
- { scope: "repository:#{project.path_with_namespace}:pull,push" }
+ context 'for internal user' do
+ context 'allow anyone to pull images' do
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:pull" }
+ end
+
+ it_behaves_like 'a pullable'
+ end
+
+ context 'disallow anyone to push images' do
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:push" }
+ end
+
+ it_behaves_like 'an inaccessible'
+ end
end
- it_behaves_like 'an inaccessible'
+ context 'for external user' do
+ let(:current_user) { create(:user, external: true) }
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:pull,push" }
+ end
+
+ it_behaves_like 'an inaccessible'
+ end
end
end
diff --git a/spec/services/ci/create_trigger_request_service_spec.rb b/spec/services/ci/create_trigger_request_service_spec.rb
index ae4b7aca820..b72e0bd3dbe 100644
--- a/spec/services/ci/create_trigger_request_service_spec.rb
+++ b/spec/services/ci/create_trigger_request_service_spec.rb
@@ -9,7 +9,7 @@ describe Ci::CreateTriggerRequestService, services: true do
stub_ci_pipeline_to_return_yaml_file
end
- describe :execute do
+ describe '#execute' do
context 'valid params' do
subject { service.execute(project, trigger, 'master') }
diff --git a/spec/services/ci/image_for_build_service_spec.rb b/spec/services/ci/image_for_build_service_spec.rb
index 476a888e394..3a3e3efe709 100644
--- a/spec/services/ci/image_for_build_service_spec.rb
+++ b/spec/services/ci/image_for_build_service_spec.rb
@@ -8,7 +8,7 @@ module Ci
let(:commit) { project.ensure_pipeline(commit_sha, 'master') }
let(:build) { FactoryGirl.create(:ci_build, pipeline: commit) }
- describe :execute do
+ describe '#execute' do
before { build }
context 'branch name' do
diff --git a/spec/services/ci/register_build_service_spec.rb b/spec/services/ci/register_build_service_spec.rb
index f28f2f1438d..026d0ca6534 100644
--- a/spec/services/ci/register_build_service_spec.rb
+++ b/spec/services/ci/register_build_service_spec.rb
@@ -13,7 +13,7 @@ module Ci
specific_runner.assign_to(project)
end
- describe :execute do
+ describe '#execute' do
context 'runner follow tag list' do
it "picks build with the same tag" do
pending_build.tag_list = ["linux"]
diff --git a/spec/services/create_commit_builds_service_spec.rb b/spec/services/create_commit_builds_service_spec.rb
index deab242f45a..d4c5e584421 100644
--- a/spec/services/create_commit_builds_service_spec.rb
+++ b/spec/services/create_commit_builds_service_spec.rb
@@ -3,13 +3,13 @@ require 'spec_helper'
describe CreateCommitBuildsService, services: true do
let(:service) { CreateCommitBuildsService.new }
let(:project) { FactoryGirl.create(:empty_project) }
- let(:user) { nil }
+ let(:user) { create(:user) }
before do
stub_ci_pipeline_to_return_yaml_file
end
- describe :execute do
+ describe '#execute' do
context 'valid params' do
let(:pipeline) do
service.execute(project, user,
@@ -24,6 +24,7 @@ describe CreateCommitBuildsService, services: true do
it { expect(pipeline).to be_valid }
it { expect(pipeline).to be_persisted }
it { expect(pipeline).to eq(project.pipelines.last) }
+ it { expect(pipeline).to have_attributes(user: user) }
it { expect(pipeline.builds.first).to be_kind_of(Ci::Build) }
end
@@ -83,6 +84,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 +100,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_deployment_service_spec.rb b/spec/services/create_deployment_service_spec.rb
index 654e441f3cd..8da2a2b3c1b 100644
--- a/spec/services/create_deployment_service_spec.rb
+++ b/spec/services/create_deployment_service_spec.rb
@@ -89,6 +89,12 @@ describe CreateDeploymentService, services: true do
expect_any_instance_of(described_class).to receive(:execute)
subject
end
+
+ it 'is set as deployable' do
+ subject
+
+ expect(Deployment.last.deployable).to eq(deployable)
+ end
end
context 'without environment specified' do
@@ -105,6 +111,8 @@ describe CreateDeploymentService, services: true do
context 'when build succeeds' do
it_behaves_like 'does create environment and deployment' do
+ let(:deployable) { build }
+
subject { build.success }
end
end
@@ -114,6 +122,14 @@ describe CreateDeploymentService, services: true do
subject { build.drop }
end
end
+
+ context 'when build is retried' do
+ it_behaves_like 'does create environment and deployment' do
+ let(:deployable) { Ci::Build.retry(build) }
+
+ subject { deployable.success }
+ end
+ end
end
end
end
diff --git a/spec/services/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/event_create_service_spec.rb b/spec/services/event_create_service_spec.rb
index f6dc9d4008f..789836f71bb 100644
--- a/spec/services/event_create_service_spec.rb
+++ b/spec/services/event_create_service_spec.rb
@@ -4,7 +4,7 @@ describe EventCreateService, services: true do
let(:service) { EventCreateService.new }
describe 'Issues' do
- describe :open_issue do
+ describe '#open_issue' do
let(:issue) { create(:issue) }
it { expect(service.open_issue(issue, issue.author)).to be_truthy }
@@ -14,7 +14,7 @@ describe EventCreateService, services: true do
end
end
- describe :close_issue do
+ describe '#close_issue' do
let(:issue) { create(:issue) }
it { expect(service.close_issue(issue, issue.author)).to be_truthy }
@@ -24,7 +24,7 @@ describe EventCreateService, services: true do
end
end
- describe :reopen_issue do
+ describe '#reopen_issue' do
let(:issue) { create(:issue) }
it { expect(service.reopen_issue(issue, issue.author)).to be_truthy }
@@ -36,7 +36,7 @@ describe EventCreateService, services: true do
end
describe 'Merge Requests' do
- describe :open_mr do
+ describe '#open_mr' do
let(:merge_request) { create(:merge_request) }
it { expect(service.open_mr(merge_request, merge_request.author)).to be_truthy }
@@ -46,7 +46,7 @@ describe EventCreateService, services: true do
end
end
- describe :close_mr do
+ describe '#close_mr' do
let(:merge_request) { create(:merge_request) }
it { expect(service.close_mr(merge_request, merge_request.author)).to be_truthy }
@@ -56,7 +56,7 @@ describe EventCreateService, services: true do
end
end
- describe :merge_mr do
+ describe '#merge_mr' do
let(:merge_request) { create(:merge_request) }
it { expect(service.merge_mr(merge_request, merge_request.author)).to be_truthy }
@@ -66,7 +66,7 @@ describe EventCreateService, services: true do
end
end
- describe :reopen_mr do
+ describe '#reopen_mr' do
let(:merge_request) { create(:merge_request) }
it { expect(service.reopen_mr(merge_request, merge_request.author)).to be_truthy }
@@ -80,7 +80,7 @@ describe EventCreateService, services: true do
describe 'Milestone' do
let(:user) { create :user }
- describe :open_milestone do
+ describe '#open_milestone' do
let(:milestone) { create(:milestone) }
it { expect(service.open_milestone(milestone, user)).to be_truthy }
@@ -90,7 +90,7 @@ describe EventCreateService, services: true do
end
end
- describe :close_mr do
+ describe '#close_mr' do
let(:milestone) { create(:milestone) }
it { expect(service.close_milestone(milestone, user)).to be_truthy }
@@ -100,7 +100,7 @@ describe EventCreateService, services: true do
end
end
- describe :destroy_mr do
+ describe '#destroy_mr' do
let(:milestone) { create(:milestone) }
it { expect(service.destroy_milestone(milestone, user)).to be_truthy }
diff --git a/spec/services/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..47c0580e0f0 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,13 +219,12 @@ describe GitPushService, services: true do
end
end
-
describe "Webhooks" do
context "execute webhooks" do
it "when pushing a branch for the first time" do
expect(project).to receive(:execute_hooks)
expect(project.default_branch).to eq("master")
- expect(project.protected_branches).to receive(:create).with({ name: "master", developers_can_push: false })
+ expect(project.protected_branches).to receive(:create).with({ name: "master", developers_can_push: false, developers_can_merge: false })
execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' )
end
@@ -211,7 +242,17 @@ describe GitPushService, services: true do
expect(project).to receive(:execute_hooks)
expect(project.default_branch).to eq("master")
- expect(project.protected_branches).to receive(:create).with({ name: "master", developers_can_push: true })
+ expect(project.protected_branches).to receive(:create).with({ name: "master", developers_can_push: true, developers_can_merge: false })
+
+ execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master')
+ end
+
+ it "when pushing a branch for the first time with default branch protection set to 'developers can merge'" do
+ stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_MERGE)
+
+ expect(project).to receive(:execute_hooks)
+ expect(project.default_branch).to eq("master")
+ expect(project.protected_branches).to receive(:create).with({ name: "master", developers_can_push: false, developers_can_merge: true })
execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' )
end
@@ -314,6 +355,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 +496,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/issues/bulk_update_service_spec.rb b/spec/services/issues/bulk_update_service_spec.rb
index 4a689e64dc5..ba3a4dfc048 100644
--- a/spec/services/issues/bulk_update_service_spec.rb
+++ b/spec/services/issues/bulk_update_service_spec.rb
@@ -262,4 +262,42 @@ describe Issues::BulkUpdateService, services: true do
end
end
end
+
+ describe :subscribe_issues do
+ let(:issues) { create_list(:issue, 5, project: project) }
+ let(:params) do
+ {
+ subscription_event: 'subscribe',
+ issues_ids: issues.map(&:id).join(',')
+ }
+ end
+
+ it 'subscribes the given user' do
+ issues.each do |issue|
+ expect(issue.subscribed?(user)).to be_truthy
+ end
+ end
+ end
+
+ describe :unsubscribe_issues do
+ let(:issues) { create_list(:closed_issue, 5, project: project) }
+ let(:params) do
+ {
+ subscription_event: 'unsubscribe',
+ issues_ids: issues.map(&:id).join(',')
+ }
+ end
+
+ before do
+ issues.each do |issue|
+ issue.subscriptions.create(user: user, subscribed: true)
+ end
+ end
+
+ it 'unsubscribes the given user' do
+ issues.each do |issue|
+ expect(issue.subscribed?(user)).to be_falsey
+ end
+ end
+ end
end
diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb
index 62b25709a5d..67a919ba8ee 100644
--- a/spec/services/issues/close_service_spec.rb
+++ b/spec/services/issues/close_service_spec.rb
@@ -12,7 +12,7 @@ describe Issues::CloseService, services: true do
project.team << [user2, :developer]
end
- describe :execute do
+ describe '#execute' do
context "valid params" do
before do
perform_enqueued_jobs do
diff --git a/spec/services/members/destroy_service_spec.rb b/spec/services/members/destroy_service_spec.rb
new file mode 100644
index 00000000000..2395445e7fd
--- /dev/null
+++ b/spec/services/members/destroy_service_spec.rb
@@ -0,0 +1,71 @@
+require 'spec_helper'
+
+describe Members::DestroyService, services: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let!(:member) { create(:project_member, source: project) }
+
+ context 'when member is nil' do
+ before do
+ project.team << [user, :developer]
+ end
+
+ it 'does not destroy the member' do
+ expect { destroy_member(nil, user) }.to raise_error(Gitlab::Access::AccessDeniedError)
+ end
+ end
+
+ context 'when current user cannot destroy the given member' do
+ before do
+ project.team << [user, :developer]
+ end
+
+ it 'does not destroy the member' do
+ expect { destroy_member(member, user) }.to raise_error(Gitlab::Access::AccessDeniedError)
+ end
+ end
+
+ context 'when current user can destroy the given member' do
+ before do
+ project.team << [user, :master]
+ end
+
+ it 'destroys the member' do
+ destroy_member(member, user)
+
+ expect(member).to be_destroyed
+ end
+
+ context 'when the given member is a requester' do
+ before do
+ member.update_column(:requested_at, Time.now)
+ end
+
+ it 'calls Member#after_decline_request' do
+ expect_any_instance_of(NotificationService).to receive(:decline_access_request).with(member)
+
+ destroy_member(member, user)
+ end
+
+ context 'when current user is the member' do
+ it 'does not call Member#after_decline_request' do
+ expect_any_instance_of(NotificationService).not_to receive(:decline_access_request).with(member)
+
+ destroy_member(member, member.user)
+ end
+ end
+
+ context 'when current user is the member and ' do
+ it 'does not call Member#after_decline_request' do
+ expect_any_instance_of(NotificationService).not_to receive(:decline_access_request).with(member)
+
+ destroy_member(member, member.user)
+ end
+ end
+ end
+ end
+
+ def destroy_member(member, user)
+ Members::DestroyService.new(member, user).execute
+ end
+end
diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb
index 8443a00e70c..c1db4f3284b 100644
--- a/spec/services/merge_requests/close_service_spec.rb
+++ b/spec/services/merge_requests/close_service_spec.rb
@@ -12,7 +12,7 @@ describe MergeRequests::CloseService, services: true do
project.team << [user2, :developer]
end
- describe :execute do
+ describe '#execute' do
context 'valid params' do
let(:service) { MergeRequests::CloseService.new(project, user, {}) }
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index e433f49872d..d0b55d2d509 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -5,7 +5,7 @@ describe MergeRequests::CreateService, services: true do
let(:user) { create(:user) }
let(:assignee) { create(:user) }
- describe :execute do
+ describe '#execute' do
context 'valid params' do
let(:opts) do
{
diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb
index 1b0396eb686..f5bf3c1e367 100644
--- a/spec/services/merge_requests/merge_service_spec.rb
+++ b/spec/services/merge_requests/merge_service_spec.rb
@@ -11,7 +11,7 @@ describe MergeRequests::MergeService, services: true do
project.team << [user2, :developer]
end
- describe :execute do
+ describe '#execute' do
context 'valid params' do
let(:service) { MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message') }
@@ -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..ce643b3f860 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -5,7 +5,7 @@ describe MergeRequests::RefreshService, services: true do
let(:user) { create(:user) }
let(:service) { MergeRequests::RefreshService }
- describe :execute do
+ describe '#execute' do
before do
@user = create(:user)
group = create(:group)
@@ -88,8 +88,7 @@ describe MergeRequests::RefreshService, services: true do
# Merge master -> feature branch
author = { email: 'test@gitlab.com', time: Time.now, name: "Me" }
commit_options = { message: 'Test message', committer: author, author: author }
- master_commit = @project.repository.commit('master')
- @project.repository.merge(@user, master_commit.id, 'feature', commit_options)
+ @project.repository.merge(@user, @merge_request, commit_options)
commit = @project.repository.commit('feature')
service.new(@project, @user).execute(@oldrev, commit.id, 'refs/heads/feature')
reload_mrs
@@ -175,7 +174,6 @@ describe MergeRequests::RefreshService, services: true do
end
end
-
def reload_mrs
@merge_request.reload
@fork_merge_request.reload
diff --git a/spec/services/merge_requests/reopen_service_spec.rb b/spec/services/merge_requests/reopen_service_spec.rb
index ac0221998f5..88c9c640514 100644
--- a/spec/services/merge_requests/reopen_service_spec.rb
+++ b/spec/services/merge_requests/reopen_service_spec.rb
@@ -11,7 +11,7 @@ describe MergeRequests::ReopenService, services: true do
project.team << [user2, :developer]
end
- describe :execute do
+ describe '#execute' do
context 'valid params' do
let(:service) { MergeRequests::ReopenService.new(project, user, {}) }
diff --git a/spec/services/milestones/close_service_spec.rb b/spec/services/milestones/close_service_spec.rb
index 1cd6eb2ab38..5d400299be0 100644
--- a/spec/services/milestones/close_service_spec.rb
+++ b/spec/services/milestones/close_service_spec.rb
@@ -9,7 +9,7 @@ describe Milestones::CloseService, services: true do
project.team << [user, :master]
end
- describe :execute do
+ describe '#execute' do
before do
Milestones::CloseService.new(project, user, {}).execute(milestone)
end
diff --git a/spec/services/milestones/create_service_spec.rb b/spec/services/milestones/create_service_spec.rb
index c793026e300..6d29edb449a 100644
--- a/spec/services/milestones/create_service_spec.rb
+++ b/spec/services/milestones/create_service_spec.rb
@@ -4,7 +4,7 @@ describe Milestones::CreateService, services: true do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
- describe :execute do
+ describe '#execute' do
context "valid params" do
before do
project.team << [user, :master]
diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index 35f576874b8..32753e84b31 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -5,7 +5,7 @@ describe Notes::CreateService, services: true do
let(:issue) { create(:issue, project: project) }
let(:user) { create(:user) }
- describe :execute do
+ describe '#execute' do
context "valid params" do
before do
project.team << [user, :master]
diff --git a/spec/services/notes/diff_position_update_service_spec.rb b/spec/services/notes/diff_position_update_service_spec.rb
new file mode 100644
index 00000000000..110efb54fa0
--- /dev/null
+++ b/spec/services/notes/diff_position_update_service_spec.rb
@@ -0,0 +1,175 @@
+require 'spec_helper'
+
+describe Notes::DiffPositionUpdateService, services: true do
+ let(:project) { create(:project) }
+ let(:create_commit) { project.commit("913c66a37b4a45b9769037c55c2d238bd0942d2e") }
+ let(:modify_commit) { project.commit("874797c3a73b60d2187ed6e2fcabd289ff75171e") }
+ let(:edit_commit) { project.commit("570e7b2abdd848b95f2f578043fc23bd6f6fd24d") }
+
+ let(:path) { "files/ruby/popen.rb" }
+
+ let(:old_diff_refs) do
+ Gitlab::Diff::DiffRefs.new(
+ base_sha: create_commit.parent_id,
+ head_sha: modify_commit.sha
+ )
+ end
+
+ let(:new_diff_refs) do
+ Gitlab::Diff::DiffRefs.new(
+ base_sha: create_commit.parent_id,
+ head_sha: edit_commit.sha
+ )
+ end
+
+ subject do
+ described_class.new(
+ project,
+ nil,
+ old_diff_refs: old_diff_refs,
+ new_diff_refs: new_diff_refs,
+ paths: [path]
+ )
+ end
+
+ # old diff:
+ # 1 + require 'fileutils'
+ # 2 + require 'open3'
+ # 3 +
+ # 4 + module Popen
+ # 5 + extend self
+ # 6 +
+ # 7 + def popen(cmd, path=nil)
+ # 8 + unless cmd.is_a?(Array)
+ # 9 + raise "System commands must be given as an array of strings"
+ # 10 + end
+ # 11 +
+ # 12 + path ||= Dir.pwd
+ # 13 + vars = { "PWD" => path }
+ # 14 + options = { chdir: path }
+ # 15 +
+ # 16 + unless File.directory?(path)
+ # 17 + FileUtils.mkdir_p(path)
+ # 18 + end
+ # 19 +
+ # 20 + @cmd_output = ""
+ # 21 + @cmd_status = 0
+ # 22 + Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
+ # 23 + @cmd_output << stdout.read
+ # 24 + @cmd_output << stderr.read
+ # 25 + @cmd_status = wait_thr.value.exitstatus
+ # 26 + end
+ # 27 +
+ # 28 + return @cmd_output, @cmd_status
+ # 29 + end
+ # 30 + end
+ #
+ # new diff:
+ # 1 + require 'fileutils'
+ # 2 + require 'open3'
+ # 3 +
+ # 4 + module Popen
+ # 5 + extend self
+ # 6 +
+ # 7 + def popen(cmd, path=nil)
+ # 8 + unless cmd.is_a?(Array)
+ # 9 + raise RuntimeError, "System commands must be given as an array of strings"
+ # 10 + end
+ # 11 +
+ # 12 + path ||= Dir.pwd
+ # 13 +
+ # 14 + vars = {
+ # 15 + "PWD" => path
+ # 16 + }
+ # 17 +
+ # 18 + options = {
+ # 19 + chdir: path
+ # 20 + }
+ # 21 +
+ # 22 + unless File.directory?(path)
+ # 23 + FileUtils.mkdir_p(path)
+ # 24 + end
+ # 25 +
+ # 26 + @cmd_output = ""
+ # 27 + @cmd_status = 0
+ # 28 +
+ # 29 + Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
+ # 30 + @cmd_output << stdout.read
+ # 31 + @cmd_output << stderr.read
+ # 32 + @cmd_status = wait_thr.value.exitstatus
+ # 33 + end
+ # 34 +
+ # 35 + return @cmd_output, @cmd_status
+ # 36 + end
+ # 37 + end
+ #
+ # old->new diff:
+ # .. .. @@ -6,12 +6,18 @@ module Popen
+ # 6 6
+ # 7 7 def popen(cmd, path=nil)
+ # 8 8 unless cmd.is_a?(Array)
+ # 9 - raise "System commands must be given as an array of strings"
+ # 9 + raise RuntimeError, "System commands must be given as an array of strings"
+ # 10 10 end
+ # 11 11
+ # 12 12 path ||= Dir.pwd
+ # 13 - vars = { "PWD" => path }
+ # 14 - options = { chdir: path }
+ # 13 +
+ # 14 + vars = {
+ # 15 + "PWD" => path
+ # 16 + }
+ # 17 +
+ # 18 + options = {
+ # 19 + chdir: path
+ # 20 + }
+ # 15 21
+ # 16 22 unless File.directory?(path)
+ # 17 23 FileUtils.mkdir_p(path)
+ # 18 24 end
+ # 19 25
+ # 20 26 @cmd_output = ""
+ # 21 27 @cmd_status = 0
+ # 28 +
+ # 22 29 Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
+ # 23 30 @cmd_output << stdout.read
+ # 24 31 @cmd_output << stderr.read
+ # .. ..
+
+ describe "#execute" do
+ let(:note) { create(:diff_note_on_merge_request, project: project, position: old_position) }
+
+ let(:old_position) do
+ Gitlab::Diff::Position.new(
+ old_path: path,
+ new_path: path,
+ old_line: nil,
+ new_line: line,
+ diff_refs: old_diff_refs
+ )
+ end
+
+ context "when the diff line is the same" do
+ let(:line) { 16 }
+
+ it "updates the position" do
+ subject.execute(note)
+
+ expect(note.original_position).to eq(old_position)
+ expect(note.position).not_to eq(old_position)
+ expect(note.position.new_line).to eq(22)
+ end
+ end
+
+ context "when the diff line has changed" do
+ let(:line) { 9 }
+
+ it "doesn't update the position" do
+ subject.execute(note)
+
+ expect(note.original_position).to eq(old_position)
+ expect(note.position).to eq(old_position)
+ end
+ end
+ end
+end
diff --git a/spec/services/notes/post_process_service_spec.rb b/spec/services/notes/post_process_service_spec.rb
index d4c50f824c1..e33a611929b 100644
--- a/spec/services/notes/post_process_service_spec.rb
+++ b/spec/services/notes/post_process_service_spec.rb
@@ -5,7 +5,7 @@ describe Notes::PostProcessService, services: true do
let(:issue) { create(:issue, project: project) }
let(:user) { create(:user) }
- describe :execute do
+ describe '#execute' do
before do
project.team << [user, :master]
note_opts = {
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 776a6ab5edb..9fc93f325f7 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -50,7 +50,7 @@ describe NotificationService, services: true do
update_custom_notification(:new_note, @u_custom_global)
end
- describe :new_note do
+ describe '#new_note' do
it do
add_users_with_subscription(note.project, issue)
@@ -95,7 +95,6 @@ describe NotificationService, services: true do
notification.new_note(note)
end
-
it { should_not_email(@u_lazy_participant) }
end
end
@@ -294,6 +293,30 @@ describe NotificationService, services: true do
end
end
end
+
+ context "merge request diff note" do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:merge_request) { create(:merge_request, source_project: project, assignee: user) }
+ let(:note) { create(:diff_note_on_merge_request, project: project, noteable: merge_request) }
+
+ before do
+ build_team(note.project)
+ project.team << [merge_request.author, :master]
+ project.team << [merge_request.assignee, :master]
+ end
+
+ describe '#new_note' do
+ it "records sent notifications" do
+ # Ensure create SentNotification by noteable = merge_request 6 times, not noteable = note
+ expect(SentNotification).to receive(:record_note).with(note, any_args).exactly(4).times.and_call_original
+
+ notification.new_note(note)
+
+ expect(SentNotification.last.position).to eq(note.position)
+ end
+ end
+ end
end
describe 'Issues' do
@@ -377,7 +400,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 +588,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 +733,6 @@ describe NotificationService, services: true do
should_email(subscriber)
end
-
context 'participating' do
context 'by assignee' do
before do
@@ -880,7 +900,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..ad0d58672b3 100644
--- a/spec/services/projects/housekeeping_service_spec.rb
+++ b/spec/services/projects/housekeeping_service_spec.rb
@@ -12,18 +12,28 @@ describe Projects::HousekeepingService do
it 'enqueues a sidekiq job' do
expect(subject).to receive(:try_obtain_lease).and_return(true)
- expect(GitlabShellOneShotWorker).to receive(:perform_async).with(:gc, project.path_with_namespace)
+ expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id)
subject.execute
- expect(project.pushes_since_gc).to eq(0)
+ expect(project.reload.pushes_since_gc).to eq(0)
end
- it 'does not enqueue a job when no lease can be obtained' do
- expect(subject).to receive(:try_obtain_lease).and_return(false)
- expect(GitlabShellOneShotWorker).not_to receive(:perform_async)
+ context 'when no lease can be obtained' do
+ before(:each) do
+ expect(subject).to receive(:try_obtain_lease).and_return(false)
+ end
- expect { subject.execute }.to raise_error(Projects::HousekeepingService::LeaseTaken)
- expect(project.pushes_since_gc).to eq(0)
+ it 'does not enqueue a job' do
+ expect(GitGarbageCollectWorker).not_to receive(:perform_async)
+
+ expect { subject.execute }.to raise_error(Projects::HousekeepingService::LeaseTaken)
+ end
+
+ it 'does not reset pushes_since_gc' do
+ expect do
+ expect { subject.execute }.to raise_error(Projects::HousekeepingService::LeaseTaken)
+ end.not_to change { project.pushes_since_gc }.from(3)
+ end
end
end
@@ -39,10 +49,24 @@ describe Projects::HousekeepingService do
end
describe 'increment!' do
+ let(:lease_key) { "project_housekeeping:increment!:#{project.id}" }
+
it 'increments the pushes_since_gc counter' do
- expect(project.pushes_since_gc).to eq(0)
- subject.increment!
- expect(project.pushes_since_gc).to eq(1)
+ lease = double(:lease, try_obtain: true)
+ expect(Gitlab::ExclusiveLease).to receive(:new).with(lease_key, anything).and_return(lease)
+
+ expect do
+ subject.increment!
+ end.to change { project.pushes_since_gc }.from(0).to(1)
+ end
+
+ it 'does not increment when no lease can be obtained' do
+ lease = double(:lease, try_obtain: false)
+ expect(Gitlab::ExclusiveLease).to receive(:new).with(lease_key, anything).and_return(lease)
+
+ expect do
+ subject.increment!
+ end.not_to change { project.pushes_since_gc }
end
end
end
diff --git a/spec/services/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/services/test_hook_service_spec.rb b/spec/services/test_hook_service_spec.rb
index f034f251ba4..4f47e89b4b5 100644
--- a/spec/services/test_hook_service_spec.rb
+++ b/spec/services/test_hook_service_spec.rb
@@ -5,7 +5,7 @@ describe TestHookService, services: true do
let(:project) { create :project }
let(:hook) { create :project_hook, project: project }
- describe :execute do
+ describe '#execute' do
it "should execute successfully" do
stub_request(:post, hook.url).to_return(status: 200)
expect(TestHookService.new.execute(hook, user)).to be_truthy
diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb
index b4522536724..34d8ea9090e 100644
--- a/spec/services/todo_service_spec.rb
+++ b/spec/services/todo_service_spec.rb
@@ -35,8 +35,11 @@ describe TodoService, services: true do
should_not_create_any_todo { service.new_issue(unassigned_issue, author) }
end
- it 'does not create a todo if assignee is the current user' do
- should_not_create_any_todo { service.new_issue(unassigned_issue, john_doe) }
+ it 'creates a todo if assignee is the current user' do
+ unassigned_issue.update_attribute(:assignee, john_doe)
+ service.new_issue(unassigned_issue, john_doe)
+
+ should_create_todo(user: john_doe, target: unassigned_issue, author: john_doe, action: Todo::ASSIGNED)
end
it 'creates a todo for each valid mentioned user' do
@@ -44,7 +47,7 @@ describe TodoService, services: true do
should_create_todo(user: member, target: issue, action: Todo::MENTIONED)
should_create_todo(user: guest, target: issue, action: Todo::MENTIONED)
- should_not_create_todo(user: author, target: issue, action: Todo::MENTIONED)
+ should_create_todo(user: author, target: issue, action: Todo::MENTIONED)
should_not_create_todo(user: john_doe, target: issue, action: Todo::MENTIONED)
should_not_create_todo(user: non_member, target: issue, action: Todo::MENTIONED)
end
@@ -57,7 +60,7 @@ describe TodoService, services: true do
should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
should_not_create_todo(user: guest, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
- should_not_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
+ should_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
end
context 'when a private group is mentioned' do
@@ -87,7 +90,7 @@ describe TodoService, services: true do
should_create_todo(user: member, target: issue, action: Todo::MENTIONED)
should_create_todo(user: guest, target: issue, action: Todo::MENTIONED)
should_create_todo(user: john_doe, target: issue, action: Todo::MENTIONED)
- should_not_create_todo(user: author, target: issue, action: Todo::MENTIONED)
+ should_create_todo(user: author, target: issue, action: Todo::MENTIONED)
should_not_create_todo(user: non_member, target: issue, action: Todo::MENTIONED)
end
@@ -105,7 +108,7 @@ describe TodoService, services: true do
should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
should_not_create_todo(user: guest, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
- should_not_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
+ should_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
end
context 'issues with a task list' do
@@ -156,10 +159,11 @@ describe TodoService, services: true do
should_not_create_any_todo { service.reassigned_issue(issue, author) }
end
- it 'does not create a todo if new assignee is the current user' do
+ it 'creates a todo if new assignee is the current user' do
unassigned_issue.update_attribute(:assignee, john_doe)
+ service.reassigned_issue(unassigned_issue, john_doe)
- should_not_create_any_todo { service.reassigned_issue(unassigned_issue, john_doe) }
+ should_create_todo(user: john_doe, target: unassigned_issue, author: john_doe, action: Todo::ASSIGNED)
end
end
@@ -250,7 +254,7 @@ describe TodoService, services: true do
should_create_todo(user: member, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
should_create_todo(user: guest, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
should_create_todo(user: author, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
- should_not_create_todo(user: john_doe, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
+ should_create_todo(user: john_doe, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
should_not_create_todo(user: non_member, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
end
@@ -262,7 +266,7 @@ describe TodoService, services: true do
should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
should_not_create_todo(user: guest, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
- should_not_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
+ should_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
end
it 'creates a todo for each valid mentioned user when leaving a note on commit' do
@@ -270,7 +274,7 @@ describe TodoService, services: true do
should_create_todo(user: member, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
should_create_todo(user: author, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
- should_not_create_todo(user: john_doe, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
+ should_create_todo(user: john_doe, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
should_not_create_todo(user: non_member, target_id: nil, target_type: 'Commit', commit_id: note_on_commit.commit_id, author: john_doe, action: Todo::MENTIONED, note: note_on_commit)
end
@@ -312,7 +316,7 @@ describe TodoService, services: true do
should_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED)
should_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED)
- should_not_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED)
+ should_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED)
should_not_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED)
should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED)
end
@@ -325,7 +329,7 @@ describe TodoService, services: true do
should_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED)
should_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED)
should_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED)
- should_not_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED)
+ should_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED)
should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED)
end
@@ -382,10 +386,11 @@ describe TodoService, services: true do
should_not_create_any_todo { service.reassigned_merge_request(mr_assigned, author) }
end
- it 'does not create a todo if new assignee is the current user' do
+ it 'creates a todo if new assignee is the current user' do
mr_assigned.update_attribute(:assignee, john_doe)
+ service.reassigned_merge_request(mr_assigned, john_doe)
- should_not_create_any_todo { service.reassigned_merge_request(mr_assigned, john_doe) }
+ should_create_todo(user: john_doe, target: mr_assigned, author: john_doe, action: Todo::ASSIGNED)
end
end
@@ -435,6 +440,24 @@ describe TodoService, services: true do
should_create_todo(user: author, target: mr_unassigned, action: Todo::MARKED)
end
end
+
+ describe '#new_note' do
+ let(:mention) { john_doe.to_reference }
+ let(:diff_note_on_merge_request) { create(:diff_note_on_merge_request, project: project, noteable: mr_unassigned, author: author, note: "Hey #{mention}") }
+ let(:legacy_diff_note_on_merge_request) { create(:legacy_diff_note_on_merge_request, project: project, noteable: mr_unassigned, author: author, note: "Hey #{mention}") }
+
+ it 'creates a todo for mentioned user on new diff note' do
+ service.new_note(diff_note_on_merge_request, author)
+
+ should_create_todo(user: john_doe, target: mr_unassigned, author: author, action: Todo::MENTIONED, note: diff_note_on_merge_request)
+ end
+
+ it 'creates a todo for mentioned user on legacy diff note' do
+ service.new_note(legacy_diff_note_on_merge_request, author)
+
+ should_create_todo(user: john_doe, target: mr_unassigned, author: author, action: Todo::MENTIONED, note: legacy_diff_note_on_merge_request)
+ end
+ end
end
it 'updates cached counts when a todo is created' do
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index b43f38ef202..3638dcbb2d3 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__)
@@ -38,7 +33,6 @@ RSpec.configure do |config|
config.include LoginHelpers, type: :request
config.include StubConfiguration
config.include EmailHelpers
- config.include RelativeUrl, type: feature
config.include TestEnv
config.include ActiveJob::TestHelper
config.include StubGitlabCalls
diff --git a/spec/support/capybara_helpers.rb b/spec/support/capybara_helpers.rb
index 9b5c3065eed..b57a3493aff 100644
--- a/spec/support/capybara_helpers.rb
+++ b/spec/support/capybara_helpers.rb
@@ -27,6 +27,14 @@ module CapybaraHelpers
end
end
end
+
+ # Refresh the page. Calling `visit current_url` doesn't seem to work consistently.
+ #
+ def refresh
+ url = current_url
+ visit 'about:blank'
+ visit url
+ end
end
RSpec.configure do |config|
diff --git a/spec/support/fake_u2f_device.rb b/spec/support/fake_u2f_device.rb
index 553fe9f1fbc..f550e9a0160 100644
--- a/spec/support/fake_u2f_device.rb
+++ b/spec/support/fake_u2f_device.rb
@@ -18,8 +18,8 @@ class FakeU2fDevice
def respond_to_u2f_authentication
app_id = @page.evaluate_script('gon.u2f.app_id')
- challenges = @page.evaluate_script('gon.u2f.challenges')
- json_response = u2f_device(app_id).sign_response(challenges[0])
+ challenge = @page.evaluate_script('gon.u2f.challenge')
+ json_response = u2f_device(app_id).sign_response(challenge)
@page.execute_script("
u2f.sign = function(appId, challenges, signRequests, callback) {
diff --git a/spec/support/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..e5f76afbfc0 100644
--- a/spec/support/login_helpers.rb
+++ b/spec/support/login_helpers.rb
@@ -37,9 +37,44 @@ module LoginHelpers
Thread.current[:current_user] = user
end
+ def login_via(provider, user, uid)
+ mock_auth_hash(provider, uid, user.email)
+ visit new_user_session_path
+ click_link provider
+ end
+
+ def mock_auth_hash(provider, uid, email)
+ # The mock_auth configuration allows you to set per-provider (or default)
+ # authentication hashes to return during integration testing.
+ OmniAuth.config.mock_auth[provider.to_sym] = OmniAuth::AuthHash.new({
+ provider: provider,
+ uid: uid,
+ info: {
+ name: 'mockuser',
+ email: email,
+ image: 'mock_user_thumbnail_url'
+ },
+ credentials: {
+ token: 'mock_token',
+ secret: 'mock_secret'
+ },
+ extra: {
+ raw_info: {
+ info: {
+ name: 'mockuser',
+ email: email,
+ image: 'mock_user_thumbnail_url'
+ }
+ }
+ }
+ })
+ Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[:saml]
+ end
+
# Requires Javascript driver.
def logout
- find(: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/omni_auth.rb b/spec/support/omni_auth.rb
new file mode 100644
index 00000000000..0b1af4052ff
--- /dev/null
+++ b/spec/support/omni_auth.rb
@@ -0,0 +1 @@
+OmniAuth.config.test_mode = true
diff --git a/spec/support/relative_url.rb b/spec/support/relative_url.rb
deleted file mode 100644
index 72e3ccce75b..00000000000
--- a/spec/support/relative_url.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# Fix route helpers in tests (e.g. root_path, ...)
-module RelativeUrl
- extend ActiveSupport::Concern
-
- included do
- default_url_options[:script_name] = Rails.application.config.relative_url_root
- end
-end
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 498bd4bf800..83f2ad96fd8 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -5,18 +5,22 @@ module TestEnv
# When developing the seed repository, comment out the branch you will modify.
BRANCH_SHA = {
- 'empty-branch' => '7efb185',
- 'flatten-dir' => 'e56497b',
- 'feature' => '0b4bc9a',
- 'feature_conflict' => 'bb5206f',
- 'fix' => '48f0be4',
- 'improve/awesome' => '5937ac0',
- 'markdown' => '0ed8c6c',
- 'lfs' => 'be93687',
- 'master' => '5937ac0',
- "'test'" => 'e56497b',
- 'orphaned-branch' => '45127a9',
- 'binary-encoding' => '7b1cf43',
+ 'empty-branch' => '7efb185',
+ 'flatten-dir' => 'e56497b',
+ 'feature' => '0b4bc9a',
+ 'feature_conflict' => 'bb5206f',
+ 'fix' => '48f0be4',
+ 'improve/awesome' => '5937ac0',
+ 'markdown' => '0ed8c6c',
+ 'lfs' => 'be93687',
+ 'master' => '5937ac0',
+ "'test'" => 'e56497b',
+ 'orphaned-branch' => '45127a9',
+ 'binary-encoding' => '7b1cf43',
+ 'gitattributes' => '5a62481',
+ 'expand-collapse-diffs' => '4842455',
+ 'expand-collapse-files' => '025db92',
+ 'expand-collapse-lines' => '238e82d'
}
# gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily
@@ -62,7 +66,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 +83,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 +130,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 +146,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..42220a20c75
--- /dev/null
+++ b/spec/views/projects/builds/show.html.haml_spec.rb
@@ -0,0 +1,52 @@
+require 'spec_helper'
+
+describe 'projects/builds/show' do
+ include Devise::TestHelpers
+
+ let(:project) { create(:project) }
+ let(:pipeline) do
+ create(:ci_pipeline, project: project,
+ sha: project.commit.id)
+ end
+ let(:build) { create(:ci_build, pipeline: pipeline) }
+
+ 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
+
+ describe 'commit title in sidebar' do
+ let(:commit_title) { project.commit.title }
+
+ it 'shows commit title and not show commit message' do
+ render
+
+ expect(rendered).to have_css('p.build-light-text.append-bottom-0',
+ text: /\A\n#{Regexp.escape(commit_title)}\n\Z/)
+ end
+ end
+end
diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb
new file mode 100644
index 00000000000..c9f5aae0815
--- /dev/null
+++ b/spec/workers/git_garbage_collect_worker_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe GitGarbageCollectWorker do
+ let(:project) { create(:project) }
+ let(:shell) { Gitlab::Shell.new }
+
+ subject { GitGarbageCollectWorker.new }
+
+ before do
+ allow(subject).to receive(:gitlab_shell).and_return(shell)
+ end
+
+ describe "#perform" do
+ it "runs `git gc`" do
+ expect(shell).to receive(:gc).with(
+ project.repository_storage_path,
+ project.path_with_namespace).
+ and_return(true)
+ expect_any_instance_of(Repository).to receive(:after_create_branch).and_call_original
+ expect_any_instance_of(Repository).to receive(:branch_names).and_call_original
+ expect_any_instance_of(Repository).to receive(:branch_count).and_call_original
+ expect_any_instance_of(Repository).to receive(:has_visible_content?).and_call_original
+
+ subject.perform(project.id)
+ end
+ end
+end
diff --git a/spec/workers/merge_worker_spec.rb b/spec/workers/merge_worker_spec.rb
index 1abd87d7d33..b5e1fdb8ded 100644
--- a/spec/workers/merge_worker_spec.rb
+++ b/spec/workers/merge_worker_spec.rb
@@ -9,7 +9,7 @@ describe MergeWorker do
before do
source_project.team << [author, :master]
- source_project.repository.expire_branch_names
+ source_project.repository.expire_branches_cache
end
it 'clears cache of source repo after removing source branch' do
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 a8368751267..e5df7b9150e 100644
--- a/vendor/gitignore/Android.gitignore
+++ b/vendor/gitignore/Android.gitignore
@@ -2,7 +2,7 @@
*.apk
*.ap_
-# Files for the Dalvik VM
+# Files for the ART/Dalvik VM
*.dex
# Java class files
@@ -34,6 +34,8 @@ captures/
# Intellij
*.iml
+.idea/workspace.xml
+.idea/libraries
# Keystore files
*.jks
diff --git a/vendor/gitignore/C++.gitignore b/vendor/gitignore/C++.gitignore
index b8bd0267bdf..259148fa18f 100644
--- a/vendor/gitignore/C++.gitignore
+++ b/vendor/gitignore/C++.gitignore
@@ -1,3 +1,6 @@
+# Prerequisites
+*.d
+
# Compiled Object files
*.slo
*.lo
@@ -15,6 +18,7 @@
# Fortran module files
*.mod
+*.smod
# Compiled Static libraries
*.lai
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/CMake.gitignore b/vendor/gitignore/CMake.gitignore
index b558e9afa6d..0cc7e4b5275 100644
--- a/vendor/gitignore/CMake.gitignore
+++ b/vendor/gitignore/CMake.gitignore
@@ -4,3 +4,4 @@ CMakeScripts
Makefile
cmake_install.cmake
install_manifest.txt
+CTestTestfile.cmake
diff --git a/vendor/gitignore/D.gitignore b/vendor/gitignore/D.gitignore
index b4433f8a512..74b926fc901 100644
--- a/vendor/gitignore/D.gitignore
+++ b/vendor/gitignore/D.gitignore
@@ -18,3 +18,7 @@
.dub
docs.json
__dummy.html
+docs/
+
+# Code coverage
+*.lst
diff --git a/vendor/gitignore/Global/Bazaar.gitignore b/vendor/gitignore/Global/Bazaar.gitignore
new file mode 100644
index 00000000000..3cbbcbd11ec
--- /dev/null
+++ b/vendor/gitignore/Global/Bazaar.gitignore
@@ -0,0 +1,2 @@
+.bzr/
+.bzrignore
diff --git a/vendor/gitignore/Global/OSX.gitignore b/vendor/gitignore/Global/OSX.gitignore
index 660b31353e8..5972fe50f66 100644
--- a/vendor/gitignore/Global/OSX.gitignore
+++ b/vendor/gitignore/Global/OSX.gitignore
@@ -1,4 +1,4 @@
-.DS_Store
+*.DS_Store
.AppleDouble
.LSOverride
@@ -15,6 +15,7 @@ Icon
.TemporaryItems
.Trashes
.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
diff --git a/vendor/gitignore/Global/README.md b/vendor/gitignore/Global/README.md
new file mode 100644
index 00000000000..06b6649bd9a
--- /dev/null
+++ b/vendor/gitignore/Global/README.md
@@ -0,0 +1,10 @@
+## Globally Useful gitignores
+
+This directory contains globally useful gitignores,
+e.g. OS-specific and editor specific.
+
+For more on global gitignores:
+<https://help.github.com/articles/ignoring-files/#create-a-global-gitignore>
+
+And a good blog post about 'em:
+<http://augustl.com/blog/2009/global_gitignores>
diff --git a/vendor/gitignore/Global/SublimeText.gitignore b/vendor/gitignore/Global/SublimeText.gitignore
index 1d4e6137591..69c8c2b29ce 100644
--- a/vendor/gitignore/Global/SublimeText.gitignore
+++ b/vendor/gitignore/Global/SublimeText.gitignore
@@ -12,3 +12,16 @@
# sftp configuration file
sftp-config.json
+
+# Package control specific files
+Package Control.last-run
+Package Control.ca-list
+Package Control.ca-bundle
+Package Control.system-ca-bundle
+Package Control.cache/
+Package Control.ca-certs/
+bh_unicode_properties.cache
+
+# Sublime-github package stores a github token in this file
+# https://packagecontrol.io/packages/sublime-github
+GitHub.sublime-settings
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/Haskell.gitignore b/vendor/gitignore/Haskell.gitignore
index 096abdd90b3..a4ee41ab62b 100644
--- a/vendor/gitignore/Haskell.gitignore
+++ b/vendor/gitignore/Haskell.gitignore
@@ -16,3 +16,4 @@ cabal.sandbox.config
*.hp
*.eventlog
.stack-work/
+cabal.project.local
diff --git a/vendor/gitignore/Julia.gitignore b/vendor/gitignore/Julia.gitignore
new file mode 100644
index 00000000000..381e0b6d252
--- /dev/null
+++ b/vendor/gitignore/Julia.gitignore
@@ -0,0 +1,4 @@
+*.jl.cov
+*.jl.*.cov
+*.jl.mem
+deps/deps.jl
diff --git a/vendor/gitignore/LICENSE b/vendor/gitignore/LICENSE
new file mode 100644
index 00000000000..0e259d42c99
--- /dev/null
+++ b/vendor/gitignore/LICENSE
@@ -0,0 +1,121 @@
+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/Laravel.gitignore b/vendor/gitignore/Laravel.gitignore
index c491fa2bc6f..1cd717b6921 100644
--- a/vendor/gitignore/Laravel.gitignore
+++ b/vendor/gitignore/Laravel.gitignore
@@ -7,7 +7,6 @@ app/storage/
# Laravel 5 & Lumen specific
bootstrap/cache/
-storage/
.env.*.php
.env.php
.env
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/Objective-C.gitignore b/vendor/gitignore/Objective-C.gitignore
index 3020bc327a7..86f21d8e0ff 100644
--- a/vendor/gitignore/Objective-C.gitignore
+++ b/vendor/gitignore/Objective-C.gitignore
@@ -24,6 +24,8 @@ xcuserdata/
## Obj-C/Swift specific
*.hmap
*.ipa
+*.dSYM.zip
+*.dSYM
# CocoaPods
#
@@ -49,3 +51,10 @@ Carthage/Build
fastlane/report.xml
fastlane/screenshots
+
+#Code Injection
+#
+# After new code Injection tools there's a generated folder /iOSInjectionProject
+# https://github.com/johnno1962/injectionforxcode
+
+iOSInjectionProject/
diff --git a/vendor/gitignore/Qt.gitignore b/vendor/gitignore/Qt.gitignore
index fa24b2efee8..c7659c24f38 100644
--- a/vendor/gitignore/Qt.gitignore
+++ b/vendor/gitignore/Qt.gitignore
@@ -34,5 +34,5 @@ Makefile*
*.qmlproject.user.*
# QtCtreator CMake
-CMakeLists.txt.user
+CMakeLists.txt.user*
diff --git a/vendor/gitignore/README.md b/vendor/gitignore/README.md
deleted file mode 100644
index 43131e815cc..00000000000
--- a/vendor/gitignore/README.md
+++ /dev/null
@@ -1,14 +0,0 @@
-# .gitignore templates
-
-This directory contains language-specific .gitignore templates that are used by GitLab.
-
-These files were automatically pulled from [this repository](https://github.com/github/gitignore).
-Please submit pull requests to that repository. There is no need to edit the files in this directory.
-
-## Bulk Update
-
-To update this directory with the latest changes in the repository, run:
-
-```sh
-bundle exec rake gitlab:update_gitignore
-```
diff --git a/vendor/gitignore/Rails.gitignore b/vendor/gitignore/Rails.gitignore
index 2121e0a8038..d8c256c1925 100644
--- a/vendor/gitignore/Rails.gitignore
+++ b/vendor/gitignore/Rails.gitignore
@@ -16,6 +16,10 @@ pickle-email-*.html
config/initializers/secret_token.rb
config/secrets.yml
+# dotenv
+# TODO Comment out this rule if environment variables can be committed
+.env
+
## Environment normalization:
/.bundle
/vendor/bundle
diff --git a/vendor/gitignore/Swift.gitignore b/vendor/gitignore/Swift.gitignore
index 8a29fa52af4..2c22487b5e3 100644
--- a/vendor/gitignore/Swift.gitignore
+++ b/vendor/gitignore/Swift.gitignore
@@ -24,6 +24,8 @@ xcuserdata/
## Obj-C/Swift specific
*.hmap
*.ipa
+*.dSYM.zip
+*.dSYM
## Playgrounds
timeline.xctimeline
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/gitignore/UnrealEngine.gitignore b/vendor/gitignore/UnrealEngine.gitignore
index 75b1186b0af..be0e4913c3a 100644
--- a/vendor/gitignore/UnrealEngine.gitignore
+++ b/vendor/gitignore/UnrealEngine.gitignore
@@ -37,6 +37,7 @@
*.suo
*.opensdf
*.sdf
+*.VC.db
*.VC.opendb
# Precompiled Assets
diff --git a/vendor/gitignore/VisualStudio.gitignore b/vendor/gitignore/VisualStudio.gitignore
index f1e3d20e056..67acbf42f5e 100644
--- a/vendor/gitignore/VisualStudio.gitignore
+++ b/vendor/gitignore/VisualStudio.gitignore
@@ -42,6 +42,7 @@ dlldata.c
# DNX
project.lock.json
+project.fragment.lock.json
artifacts/
*_i.c
diff --git a/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml b/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml
new file mode 100644
index 00000000000..396d3f1b042
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml
@@ -0,0 +1,7 @@
+# Official docker image.
+image: docker:latest
+
+build:
+ stage: build
+ script:
+ - docker build -t test .
diff --git a/vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml b/vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml
new file mode 100644
index 00000000000..0b329aaf1c4
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Elixir.gitlab-ci.yml
@@ -0,0 +1,18 @@
+# This template uses the non default language docker image
+# The image already has Hex installed. You might want to consider to use `elixir:latest`
+image: trenpixster/elixir:latest
+
+# Pic 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
+
+before_script:
+ - mix deps.get
+
+mix:
+ script:
+ - mix test
diff --git a/vendor/gitlab-ci-yml/LICENSE b/vendor/gitlab-ci-yml/LICENSE
new file mode 100644
index 00000000000..80f7b87b6c0
--- /dev/null
+++ b/vendor/gitlab-ci-yml/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 GitLab.org
+
+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.
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/Nodejs.gitlab-ci.yml b/vendor/gitlab-ci-yml/Nodejs.gitlab-ci.yml
new file mode 100644
index 00000000000..e5bce3503f3
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Nodejs.gitlab-ci.yml
@@ -0,0 +1,27 @@
+# Official framework image. Look for the different tagged releases at:
+# https://hub.docker.com/r/library/node/tags/
+image: node:latest
+
+# 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
+
+# This folder is cached between builds
+# http://docs.gitlab.com/ce/ci/yaml/README.html#cache
+cache:
+ paths:
+ - node_modules/
+
+test_async:
+ script:
+ - npm install
+ - node ./specs/start.js ./specs/async.spec.js
+
+test_db:
+ script:
+ - npm install
+ - node ./specs/start.js ./specs/db-postgres.spec.js
diff --git a/vendor/gitlab-ci-yml/Pages/Brunch.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Brunch.gitlab-ci.yml
new file mode 100644
index 00000000000..7fcc0b436b5
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/Brunch.gitlab-ci.yml
@@ -0,0 +1,16 @@
+# Full project: https://gitlab.com/pages/brunch
+image: node:4.2.2
+
+pages:
+ cache:
+ paths:
+ - node_modules/
+
+ script:
+ - npm install -g brunch
+ - brunch build --production
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
diff --git a/vendor/gitlab-ci-yml/Pages/Doxygen.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Doxygen.gitlab-ci.yml
new file mode 100644
index 00000000000..791afdd23f1
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/Doxygen.gitlab-ci.yml
@@ -0,0 +1,13 @@
+# Full project: https://gitlab.com/pages/doxygen
+image: alpine
+
+pages:
+ script:
+ - apk update && apk add doxygen
+ - doxygen doxygen/Doxyfile
+ - mv doxygen/documentation/html/ public/
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
diff --git a/vendor/gitlab-ci-yml/Pages/HTML.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/HTML.gitlab-ci.yml
new file mode 100644
index 00000000000..249a168aa33
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/HTML.gitlab-ci.yml
@@ -0,0 +1,12 @@
+# Full project: https://gitlab.com/pages/plain-html
+pages:
+ stage: deploy
+ script:
+ - mkdir .public
+ - cp -r * .public
+ - mv .public public
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
diff --git a/vendor/gitlab-ci-yml/Pages/Harp.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Harp.gitlab-ci.yml
new file mode 100644
index 00000000000..dd3ef149668
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/Harp.gitlab-ci.yml
@@ -0,0 +1,16 @@
+# Full project: https://gitlab.com/pages/harp
+image: node:4.2.2
+
+pages:
+ cache:
+ paths:
+ - node_modules
+
+ script:
+ - npm install -g harp
+ - harp compile ./ public
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
diff --git a/vendor/gitlab-ci-yml/Pages/Hexo.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Hexo.gitlab-ci.yml
new file mode 100644
index 00000000000..b468d79bcad
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/Hexo.gitlab-ci.yml
@@ -0,0 +1,25 @@
+# Full project: https://gitlab.com/pages/hexo
+image: python:2.7
+
+cache:
+ paths:
+ - vendor/
+
+test:
+ stage: test
+ script:
+ - pip install hyde
+ - hyde gen
+ except:
+ - master
+
+pages:
+ stage: deploy
+ script:
+ - pip install hyde
+ - hyde gen -d public
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
diff --git a/vendor/gitlab-ci-yml/Pages/Hugo.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Hugo.gitlab-ci.yml
new file mode 100644
index 00000000000..45df6975259
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/Hugo.gitlab-ci.yml
@@ -0,0 +1,11 @@
+# Full project: https://gitlab.com/pages/hugo
+image: publysher/hugo
+
+pages:
+ script:
+ - hugo
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
diff --git a/vendor/gitlab-ci-yml/Pages/Hyde.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Hyde.gitlab-ci.yml
new file mode 100644
index 00000000000..f5b40f2b9f1
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/Hyde.gitlab-ci.yml
@@ -0,0 +1,25 @@
+# Full project: https://gitlab.com/pages/hyde
+image: python:2.7
+
+cache:
+ paths:
+ - vendor/
+
+test:
+ stage: test
+ script:
+ - pip install hyde
+ - hyde gen
+ except:
+ - master
+
+pages:
+ stage: deploy
+ script:
+ - pip install hyde
+ - hyde gen -d public
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
diff --git a/vendor/gitlab-ci-yml/Pages/Jekyll.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Jekyll.gitlab-ci.yml
new file mode 100644
index 00000000000..36918fc005a
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/Jekyll.gitlab-ci.yml
@@ -0,0 +1,24 @@
+# Full project: https://gitlab.com/pages/jekyll
+image: ruby:2.3
+
+test:
+ stage: test
+ script:
+ - gem install jekyll
+ - jekyll build -d test
+ artifacts:
+ paths:
+ - test
+ except:
+ - master
+
+pages:
+ stage: deploy
+ script:
+ - gem install jekyll
+ - jekyll build -d public
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
diff --git a/vendor/gitlab-ci-yml/Pages/Lektor.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Lektor.gitlab-ci.yml
new file mode 100644
index 00000000000..c5c44a5d86c
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/Lektor.gitlab-ci.yml
@@ -0,0 +1,12 @@
+# Full project: https://gitlab.com/pages/hyde
+image: python:2.7
+
+pages:
+ script:
+ - pip install lektor
+ - lektor build --output-path public
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
diff --git a/vendor/gitlab-ci-yml/Pages/Metalsmith.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Metalsmith.gitlab-ci.yml
new file mode 100644
index 00000000000..50e8b7ccd46
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/Metalsmith.gitlab-ci.yml
@@ -0,0 +1,17 @@
+# Full project: https://gitlab.com/pages/metalsmith
+image: node:4.2.2
+
+pages:
+ cache:
+ paths:
+ - node_modules/
+
+ script:
+ - npm install -g metalsmith
+ - npm install
+ - make build
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
diff --git a/vendor/gitlab-ci-yml/Pages/Middleman.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Middleman.gitlab-ci.yml
new file mode 100644
index 00000000000..9f4cc0574d6
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/Middleman.gitlab-ci.yml
@@ -0,0 +1,27 @@
+# Full project: https://gitlab.com/pages/middleman
+image: ruby:2.3
+
+cache:
+ paths:
+ - vendor
+
+test:
+ script:
+ - apt-get update -yqqq
+ - apt-get install -y nodejs
+ - bundle install --path vendor
+ - bundle exec middleman build
+ except:
+ - master
+
+pages:
+ script:
+ - apt-get update -yqqq
+ - apt-get install -y nodejs
+ - bundle install --path vendor
+ - bundle exec middleman build
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
diff --git a/vendor/gitlab-ci-yml/Pages/Nanoc.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Nanoc.gitlab-ci.yml
new file mode 100644
index 00000000000..b469b316ba5
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/Nanoc.gitlab-ci.yml
@@ -0,0 +1,12 @@
+# Full project: https://gitlab.com/pages/nanoc
+image: ruby:2.3
+
+pages:
+ script:
+ - bundle install -j4
+ - nanoc
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
diff --git a/vendor/gitlab-ci-yml/Pages/Octopress.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Octopress.gitlab-ci.yml
new file mode 100644
index 00000000000..4762ec9acfd
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/Octopress.gitlab-ci.yml
@@ -0,0 +1,15 @@
+# Full project: https://gitlab.com/pages/octopress
+image: ruby:2.3
+
+pages:
+ script:
+ - apt-get update -qq && apt-get install -qq nodejs
+ - bundle install -j4
+ - bundle exec rake generate
+ - mv public .public
+ - mv .public/octopress public
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
diff --git a/vendor/gitlab-ci-yml/Pages/Pelican.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/Pelican.gitlab-ci.yml
new file mode 100644
index 00000000000..c5f3154f587
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Pages/Pelican.gitlab-ci.yml
@@ -0,0 +1,10 @@
+# Full project: https://gitlab.com/pages/pelican
+image: python:2.7-alpine
+
+pages:
+ script:
+ - pip install -r requirements.txt
+ - pelican -s publishconf.py
+ artifacts:
+ paths:
+ - public/
diff --git a/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml
new file mode 100644
index 00000000000..2a761bbd127
--- /dev/null
+++ b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml
@@ -0,0 +1,37 @@
+# Official language image. Look for the different tagged releases at:
+# https://hub.docker.com/r/library/ruby/tags/
+image: "ruby:2.3"
+
+# 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
+
+# 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:
+ - 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
+
+rspec:
+ script:
+ - rspec spec
+
+rails:
+ script:
+ - 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