summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Wyatt <wyatt.mike@gmail.com>2015-12-31 18:45:55 -0400
committerMike Wyatt <wyatt.mike@gmail.com>2015-12-31 18:45:55 -0400
commitcf4ccdda2728b6cc2879006898481d2ee786813c (patch)
tree6e20ab08ff8e329ca8725d653e4b1cb165845626
parent571df5f44bfec89b21bdce0f91f9acfdda6d7660 (diff)
parentd33cc4e53070e6afb576911aa4d76dc80eba78b7 (diff)
downloadgitlab-ce-cf4ccdda2728b6cc2879006898481d2ee786813c.tar.gz
Merge remote-tracking branch 'upstream/master' into better-asana-refs
* upstream/master: (307 commits) Update CHANGELOG spinach fix Updated allocations Gem to version 1.0.3 Removed various default metrics tags Update CHANGELOG Fix "I see current user as the first user" step Swap Author and Assignee Selectors on issuable index view Update CHANGELOG Make sure that is no pending migrations in Gitlab::CurrentSettings Added additional config environmental variables to help Debian packaging We don't use whenever anymore. Lets remove the schedule file Fix project transfer e-mail sending incorrect paths in e-mail notification Update CHANGELOG Use Gitlab::CurrentSettings for InfluxDB Write to InfluxDB directly via UDP Strip newlines from obfuscated SQL Add hotfix that allows to access build artifacts created before 8.3 note votes methids implementation When reCAPTCHA is disabled, allow registrations to go through without a code Downcased user or email search for avatar_icon. ...
-rw-r--r--.gitlab-ci.yml1
-rw-r--r--.rubocop.yml42
-rw-r--r--CHANGELOG41
-rw-r--r--CONTRIBUTING.md24
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--Gemfile33
-rw-r--r--Gemfile.lock146
-rw-r--r--VERSION2
-rw-r--r--app/assets/images/brand_logo.pngbin27059 -> 0 bytes
-rw-r--r--app/assets/images/emoji.pngbin0 -> 832902 bytes
-rw-r--r--app/assets/images/gitlab_logo.pngbin0 -> 5189 bytes
-rw-r--r--app/assets/javascripts/application.js.coffee2
-rw-r--r--app/assets/javascripts/awards_handler.coffee103
-rw-r--r--app/assets/javascripts/blob/blob_file_dropzone.js.coffee2
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee2
-rw-r--r--app/assets/javascripts/issuable_context.js.coffee2
-rw-r--r--app/assets/javascripts/issue.js.coffee42
-rw-r--r--app/assets/javascripts/merge_request.js.coffee8
-rw-r--r--app/assets/javascripts/merge_request_widget.js.coffee2
-rw-r--r--app/assets/javascripts/new_branch_form.js.coffee78
-rw-r--r--app/assets/javascripts/new_commit_form.js.coffee2
-rw-r--r--app/assets/javascripts/notes.js.coffee2
-rw-r--r--app/assets/javascripts/project.js.coffee5
-rw-r--r--app/assets/javascripts/projects_list.js.coffee10
-rw-r--r--app/assets/javascripts/shortcuts.js.coffee10
-rw-r--r--app/assets/javascripts/star.js.coffee22
-rw-r--r--app/assets/javascripts/users_select.js.coffee2
-rw-r--r--app/assets/stylesheets/application.scss6
-rw-r--r--app/assets/stylesheets/framework/blocks.scss2
-rw-r--r--app/assets/stylesheets/framework/buttons.scss39
-rw-r--r--app/assets/stylesheets/framework/common.scss12
-rw-r--r--app/assets/stylesheets/framework/issue_box.scss14
-rw-r--r--app/assets/stylesheets/framework/layout.scss2
-rw-r--r--app/assets/stylesheets/framework/lists.scss6
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss2
-rw-r--r--app/assets/stylesheets/framework/mixins.scss9
-rw-r--r--app/assets/stylesheets/framework/mobile.scss2
-rw-r--r--app/assets/stylesheets/framework/timeline.scss3
-rw-r--r--app/assets/stylesheets/framework/variables.scss31
-rw-r--r--app/assets/stylesheets/pages/awards.scss79
-rw-r--r--app/assets/stylesheets/pages/detail_page.scss33
-rw-r--r--app/assets/stylesheets/pages/emojis.scss1272
-rw-r--r--app/assets/stylesheets/pages/issuable.scss45
-rw-r--r--app/assets/stylesheets/pages/issues.scss5
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss2
-rw-r--r--app/assets/stylesheets/pages/note_form.scss6
-rw-r--r--app/assets/stylesheets/pages/notes.scss2
-rw-r--r--app/assets/stylesheets/pages/projects.scss120
-rw-r--r--app/assets/stylesheets/pages/status.scss17
-rw-r--r--app/controllers/admin/application_settings_controller.rb13
-rw-r--r--app/controllers/admin/identities_controller.rb17
-rw-r--r--app/controllers/application_controller.rb39
-rw-r--r--app/controllers/ci/lints_controller.rb4
-rw-r--r--app/controllers/concerns/creates_commit.rb103
-rw-r--r--app/controllers/concerns/creates_merge_request_for_commit.rb28
-rw-r--r--app/controllers/dashboard/snippets_controller.rb3
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb16
-rw-r--r--app/controllers/profiles/two_factor_auths_controller.rb25
-rw-r--r--app/controllers/projects/blob_controller.rb94
-rw-r--r--app/controllers/projects/commits_controller.rb2
-rw-r--r--app/controllers/projects/forks_controller.rb28
-rw-r--r--app/controllers/projects/imports_controller.rb29
-rw-r--r--app/controllers/projects/merge_requests_controller.rb11
-rw-r--r--app/controllers/projects/notes_controller.rb3
-rw-r--r--app/controllers/projects/protected_branches_controller.rb2
-rw-r--r--app/controllers/projects/services_controller.rb5
-rw-r--r--app/controllers/projects/tree_controller.rb38
-rw-r--r--app/controllers/projects_controller.rb4
-rw-r--r--app/controllers/registrations_controller.rb23
-rw-r--r--app/controllers/sessions_controller.rb18
-rw-r--r--app/helpers/application_helper.rb12
-rw-r--r--app/helpers/auth_helper.rb12
-rw-r--r--app/helpers/blob_helper.rb102
-rw-r--r--app/helpers/button_helper.rb4
-rw-r--r--app/helpers/ci_status_helper.rb22
-rw-r--r--app/helpers/external_wiki_helper.rb2
-rw-r--r--app/helpers/gitlab_markdown_helper.rb3
-rw-r--r--app/helpers/issues_helper.rb43
-rw-r--r--app/helpers/merge_requests_helper.rb11
-rw-r--r--app/helpers/page_layout_helper.rb74
-rw-r--r--app/helpers/projects_helper.rb23
-rw-r--r--app/helpers/tree_helper.rb43
-rw-r--r--app/helpers/visibility_level_helper.rb3
-rw-r--r--app/mailers/notify.rb2
-rw-r--r--app/models/ability.rb16
-rw-r--r--app/models/application_setting.rb101
-rw-r--r--app/models/ci/build.rb71
-rw-r--r--app/models/ci/commit.rb10
-rw-r--r--app/models/concerns/issuable.rb14
-rw-r--r--app/models/concerns/mentionable.rb4
-rw-r--r--app/models/concerns/participable.rb2
-rw-r--r--app/models/concerns/token_authenticatable.rb24
-rw-r--r--app/models/global_milestone.rb2
-rw-r--r--app/models/identity.rb1
-rw-r--r--app/models/issue.rb2
-rw-r--r--app/models/jira_issue.rb2
-rw-r--r--app/models/merge_request.rb6
-rw-r--r--app/models/namespace.rb4
-rw-r--r--app/models/note.rb15
-rw-r--r--app/models/project.rb53
-rw-r--r--app/models/project_services/bamboo_service.rb6
-rw-r--r--app/models/project_services/flowdock_service.rb2
-rw-r--r--app/models/project_services/gemnasium_service.rb2
-rw-r--r--app/models/project_services/gitlab_ci_service.rb7
-rw-r--r--app/models/project_services/jira_service.rb241
-rw-r--r--app/models/project_services/teamcity_service.rb8
-rw-r--r--app/models/repository.rb63
-rw-r--r--app/models/user.rb7
-rw-r--r--app/services/base_service.rb5
-rw-r--r--app/services/create_branch_service.rb19
-rw-r--r--app/services/create_commit_builds_service.rb18
-rw-r--r--app/services/files/base_service.rb26
-rw-r--r--app/services/files/create_service.rb2
-rw-r--r--app/services/issues/close_service.rb5
-rw-r--r--app/services/merge_requests/refresh_service.rb2
-rw-r--r--app/services/projects/update_service.rb28
-rw-r--r--app/services/system_note_service.rb9
-rw-r--r--app/uploaders/artifact_uploader.rb8
-rw-r--r--app/views/admin/application_settings/_form.html.haml89
-rw-r--r--app/views/admin/dashboard/index.html.haml4
-rw-r--r--app/views/admin/identities/index.html.haml1
-rw-r--r--app/views/admin/identities/new.html.haml4
-rw-r--r--app/views/admin/runners/index.html.haml2
-rw-r--r--app/views/ci/lints/_create.html.haml2
-rw-r--r--app/views/ci/lints/create.js.haml2
-rw-r--r--app/views/ci/lints/show.html.haml36
-rw-r--r--app/views/dashboard/_projects_head.html.haml27
-rw-r--r--app/views/dashboard/milestones/show.html.haml20
-rw-r--r--app/views/dashboard/projects/_projects.html.haml8
-rw-r--r--app/views/devise/mailer/unlock_instructions.html.erb7
-rw-r--r--app/views/devise/mailer/unlock_instructions.html.haml10
-rw-r--r--app/views/devise/shared/_signup_box.html.haml12
-rw-r--r--app/views/devise/unlocks/new.html.erb12
-rw-r--r--app/views/devise/unlocks/new.html.haml14
-rw-r--r--app/views/explore/projects/index.html.haml2
-rw-r--r--app/views/explore/projects/starred.html.haml2
-rw-r--r--app/views/explore/projects/trending.html.haml2
-rw-r--r--app/views/groups/_projects.html.haml4
-rw-r--r--app/views/groups/edit.html.haml2
-rw-r--r--app/views/groups/milestones/show.html.haml32
-rw-r--r--app/views/groups/show.html.haml78
-rw-r--r--app/views/help/_shortcuts.html.haml8
-rw-r--r--app/views/layouts/_head.html.haml22
-rw-r--r--app/views/notify/_note_message.html.haml2
-rw-r--r--app/views/profiles/keys/new.html.haml2
-rw-r--r--app/views/profiles/two_factor_auths/new.html.haml1
-rw-r--r--app/views/projects/_commit_button.html.haml4
-rw-r--r--app/views/projects/_home_panel.html.haml5
-rw-r--r--app/views/projects/blob/_actions.html.haml15
-rw-r--r--app/views/projects/blob/_new_dir.html.haml4
-rw-r--r--app/views/projects/blob/_upload.html.haml5
-rw-r--r--app/views/projects/blob/edit.html.haml2
-rw-r--r--app/views/projects/blob/show.html.haml2
-rw-r--r--app/views/projects/branches/new.html.haml10
-rw-r--r--app/views/projects/builds/show.html.haml4
-rw-r--r--app/views/projects/buttons/_dropdown.html.haml20
-rw-r--r--app/views/projects/buttons/_fork.html.haml5
-rw-r--r--app/views/projects/buttons/_star.html.haml22
-rw-r--r--app/views/projects/commit/_commit_box.html.haml6
-rw-r--r--app/views/projects/commit_statuses/_commit_status.html.haml8
-rw-r--r--app/views/projects/commits/_commit.html.haml2
-rw-r--r--app/views/projects/diffs/_file.html.haml7
-rw-r--r--app/views/projects/forks/new.html.haml1
-rw-r--r--app/views/projects/issues/_closed_by_box.html.haml3
-rw-r--r--app/views/projects/issues/_discussion.html.haml7
-rw-r--r--app/views/projects/issues/_merge_requests.html.haml7
-rw-r--r--app/views/projects/issues/show.html.haml101
-rw-r--r--app/views/projects/merge_requests/_discussion.html.haml7
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml2
-rw-r--r--app/views/projects/merge_requests/_new_submit.html.haml8
-rw-r--r--app/views/projects/merge_requests/_show.html.haml140
-rw-r--r--app/views/projects/merge_requests/show/_how_to_merge.html.haml12
-rw-r--r--app/views/projects/merge_requests/show/_mr_box.html.haml4
-rw-r--r--app/views/projects/merge_requests/show/_mr_title.html.haml13
-rw-r--r--app/views/projects/merge_requests/widget/_merged.html.haml12
-rw-r--r--app/views/projects/merge_requests/widget/open/_accept.html.haml24
-rw-r--r--app/views/projects/milestones/show.html.haml78
-rw-r--r--app/views/projects/notes/_form.html.haml2
-rw-r--r--app/views/projects/show.html.haml2
-rw-r--r--app/views/projects/tree/_tree_content.html.haml2
-rw-r--r--app/views/projects/tree/_tree_header.html.haml81
-rw-r--r--app/views/search/results/_issue.html.haml2
-rw-r--r--app/views/shared/_clone_panel.html.haml29
-rw-r--r--app/views/shared/_new_commit_form.html.haml28
-rw-r--r--app/views/shared/issuable/_filter.html.haml8
-rw-r--r--app/views/shared/issuable/_form.html.haml2
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml16
-rw-r--r--app/views/shared/snippets/_header.html.haml44
-rw-r--r--app/views/users/show.html.haml7
-rw-r--r--app/views/votes/_votes_block.html.haml37
-rw-r--r--config/database.yml.env9
-rw-r--r--config/gitlab.yml.example26
-rw-r--r--config/initializers/1_settings.rb24
-rw-r--r--config/initializers/carrierwave.rb10
-rw-r--r--config/initializers/devise.rb14
-rw-r--r--config/initializers/metrics.rb64
-rw-r--r--config/initializers/sidekiq.rb11
-rw-r--r--config/routes.rb5
-rw-r--r--config/schedule.rb8
-rw-r--r--config/schedule.yml10
-rw-r--r--db/migrate/20151012173029_set_jira_service_api_url.rb50
-rw-r--r--db/migrate/20151203162134_add_build_events_to_services.rb2
-rw-r--r--db/migrate/20151209144329_migrate_ci_web_hooks.rb3
-rw-r--r--db/migrate/20151210030143_add_unlock_token_to_user.rb5
-rw-r--r--db/migrate/20151210125928_add_ci_to_project.rb2
-rw-r--r--db/migrate/20151210125929_add_project_id_to_ci.rb2
-rw-r--r--db/migrate/20151210125930_migrate_ci_to_project.rb7
-rw-r--r--db/migrate/20151210125931_add_index_to_ci_tables.rb2
-rw-r--r--db/migrate/20151210125932_drop_null_for_ci_tables.rb2
-rw-r--r--db/migrate/20151218154042_add_tfa_to_application_settings.rb8
-rw-r--r--db/migrate/20151221234414_add_tfa_additional_fields.rb7
-rw-r--r--db/migrate/20151224123230_rename_emojis.rb15
-rw-r--r--db/migrate/20151228150906_influxdb_settings.rb18
-rw-r--r--db/migrate/20151228175719_add_recaptcha_to_application_settings.rb9
-rw-r--r--db/migrate/20151229102248_influxdb_udp_port_setting.rb5
-rw-r--r--db/migrate/20151229112614_influxdb_remote_database_setting.rb5
-rw-r--r--db/schema.rb81
-rw-r--r--doc/README.md13
-rw-r--r--doc/administration/enviroment_variables.md48
-rw-r--r--doc/api/merge_requests.md17
-rw-r--r--doc/api/notes.md3
-rw-r--r--doc/api/projects.md28
-rw-r--r--doc/api/users.md12
-rw-r--r--doc/ci/README.md32
-rw-r--r--doc/ci/docker/using_docker_images.md23
-rw-r--r--doc/ci/triggers/README.md172
-rw-r--r--doc/ci/triggers/img/builds_page.pngbin0 -> 39713 bytes
-rw-r--r--doc/ci/triggers/img/trigger_single_build.pngbin0 -> 2895 bytes
-rw-r--r--doc/ci/triggers/img/trigger_variables.pngbin0 -> 5418 bytes
-rw-r--r--doc/ci/triggers/img/triggers_page.pngbin0 -> 15889 bytes
-rw-r--r--doc/ci/variables/README.md2
-rw-r--r--doc/ci/yaml/README.md346
-rw-r--r--doc/install/installation.md2
-rw-r--r--doc/integration/README.md3
-rw-r--r--doc/integration/cas.md62
-rw-r--r--doc/integration/jira.md113
-rw-r--r--doc/integration/jira_issue_reference.pngbin0 -> 39942 bytes
-rw-r--r--doc/integration/jira_project_name.pngbin0 -> 60598 bytes
-rw-r--r--doc/integration/jira_service.pngbin0 -> 59082 bytes
-rw-r--r--doc/integration/jira_service_close_issue.pngbin0 -> 88433 bytes
-rw-r--r--doc/integration/jira_service_page.pngbin0 -> 162449 bytes
-rw-r--r--doc/integration/jira_workflow_screenshot.pngbin0 -> 121534 bytes
-rw-r--r--doc/integration/recaptcha.md23
-rw-r--r--doc/permissions/permissions.md6
-rw-r--r--doc/security/README.md2
-rw-r--r--doc/security/crime_vulnerability.md63
-rw-r--r--doc/security/two_factor_authentication.md38
-rw-r--r--doc/update/8.2-to-8.3.md24
-rw-r--r--doc/workflow/README.md1
-rw-r--r--doc/workflow/importing/README.md20
-rw-r--r--doc/workflow/importing/migrating_from_svn.md79
-rw-r--r--features/project/commits/branches.feature1
-rw-r--r--features/project/create.feature1
-rw-r--r--features/project/issues/award_emoji.feature15
-rw-r--r--features/project/merge_requests/accept.feature8
-rw-r--r--features/project/service.feature6
-rw-r--r--features/project/source/browse_files.feature106
-rw-r--r--features/project/star.feature1
-rw-r--r--features/steps/explore/groups.rb8
-rw-r--r--features/steps/explore/projects.rb12
-rw-r--r--features/steps/groups.rb2
-rw-r--r--features/steps/profile/profile.rb2
-rw-r--r--features/steps/project/commits/branches.rb3
-rw-r--r--features/steps/project/create.rb6
-rw-r--r--features/steps/project/forked_merge_requests.rb3
-rw-r--r--features/steps/project/issues/award_emoji.rb31
-rw-r--r--features/steps/project/issues/issues.rb9
-rw-r--r--features/steps/project/merge_requests.rb4
-rw-r--r--features/steps/project/merge_requests/acceptance.rb4
-rw-r--r--features/steps/project/project.rb2
-rw-r--r--features/steps/project/services.rb18
-rw-r--r--features/steps/project/snippets.rb2
-rw-r--r--features/steps/project/source/browse_files.rb45
-rw-r--r--features/steps/project/star.rb2
-rw-r--r--features/steps/shared/diff_note.rb2
-rw-r--r--features/steps/shared/paths.rb8
-rw-r--r--features/steps/snippets/snippets.rb2
-rw-r--r--fixtures/emojis/aliases.json367
-rw-r--r--fixtures/emojis/index.json13376
-rw-r--r--lib/api/entities.rb4
-rw-r--r--lib/api/files.rb2
-rw-r--r--lib/api/projects.rb15
-rw-r--r--lib/api/users.rb14
-rw-r--r--lib/award_emoji.rb84
-rw-r--r--lib/banzai/filter/abstract_reference_filter.rb23
-rw-r--r--lib/banzai/filter/external_issue_reference_filter.rb26
-rw-r--r--lib/banzai/filter/label_reference_filter.rb2
-rw-r--r--lib/banzai/filter/markdown_filter.rb2
-rw-r--r--lib/banzai/filter/redactor_filter.rb6
-rw-r--r--lib/banzai/filter/reference_filter.rb10
-rw-r--r--lib/banzai/filter/reference_gatherer_filter.rb8
-rw-r--r--lib/banzai/filter/table_of_contents_filter.rb2
-rw-r--r--lib/banzai/filter/user_reference_filter.rb16
-rw-r--r--lib/banzai/renderer.rb4
-rw-r--r--lib/ci/api/helpers.rb2
-rw-r--r--lib/gitlab/backend/shell.rb2
-rw-r--r--lib/gitlab/bitbucket_import/project_creator.rb3
-rw-r--r--lib/gitlab/current_settings.rb4
-rw-r--r--lib/gitlab/diff/file.rb4
-rw-r--r--lib/gitlab/fogbugz_import/importer.rb2
-rw-r--r--lib/gitlab/fogbugz_import/project_creator.rb3
-rw-r--r--lib/gitlab/git.rb4
-rw-r--r--lib/gitlab/gitlab_import/project_creator.rb3
-rw-r--r--lib/gitlab/gitorious_import/project_creator.rb3
-rw-r--r--lib/gitlab/google_code_import/importer.rb6
-rw-r--r--lib/gitlab/google_code_import/project_creator.rb3
-rw-r--r--lib/gitlab/ldap/user.rb6
-rw-r--r--lib/gitlab/metrics.rb104
-rw-r--r--lib/gitlab/metrics/delta.rb32
-rw-r--r--lib/gitlab/metrics/instrumentation.rb146
-rw-r--r--lib/gitlab/metrics/metric.rb31
-rw-r--r--lib/gitlab/metrics/obfuscated_sql.rb47
-rw-r--r--lib/gitlab/metrics/rack_middleware.rb49
-rw-r--r--lib/gitlab/metrics/sampler.rb98
-rw-r--r--lib/gitlab/metrics/sidekiq_middleware.rb23
-rw-r--r--lib/gitlab/metrics/subscribers/action_view.rb53
-rw-r--r--lib/gitlab/metrics/subscribers/active_record.rb48
-rw-r--r--lib/gitlab/metrics/system.rb35
-rw-r--r--lib/gitlab/metrics/transaction.rb66
-rw-r--r--lib/gitlab/o_auth/session.rb17
-rw-r--r--lib/gitlab/o_auth/user.rb2
-rw-r--r--lib/gitlab/recaptcha.rb14
-rw-r--r--lib/gitlab/reference_extractor.rb23
-rw-r--r--lib/gitlab/visibility_level.rb9
-rw-r--r--lib/rouge/formatters/html_gitlab.rb2
-rwxr-xr-xlib/support/init.d/gitlab13
-rwxr-xr-xlib/support/init.d/gitlab.default.example6
-rw-r--r--lib/support/nginx/gitlab146
-rw-r--r--lib/support/nginx/gitlab-ssl147
-rw-r--r--spec/controllers/projects/tree_controller_spec.rb8
-rw-r--r--spec/factories.rb3
-rw-r--r--spec/features/admin/admin_runners_spec.rb2
-rw-r--r--spec/features/ci_lint_spec.rb39
-rw-r--r--spec/features/commits_spec.rb29
-rw-r--r--spec/features/issues/filter_by_milestone_spec.rb4
-rw-r--r--spec/features/lint_spec.rb28
-rw-r--r--spec/features/login_spec.rb52
-rw-r--r--spec/features/merge_requests/merge_when_build_succeeds_spec.rb6
-rw-r--r--spec/features/projects_spec.rb14
-rw-r--r--spec/features/security/group_access_spec.rb4
-rw-r--r--spec/features/task_lists_spec.rb4
-rw-r--r--spec/helpers/application_helper_spec.rb7
-rw-r--r--spec/helpers/ci_status_helper_spec.rb11
-rw-r--r--spec/helpers/groups_helper.rb2
-rw-r--r--spec/helpers/issues_helper_spec.rb19
-rw-r--r--spec/helpers/merge_requests_helper_spec.rb41
-rw-r--r--spec/helpers/page_layout_helper_spec.rb129
-rw-r--r--spec/helpers/projects_helper_spec.rb10
-rw-r--r--spec/javascripts/fixtures/issues_show.html.haml14
-rw-r--r--spec/javascripts/fixtures/merge_requests_show.html.haml2
-rw-r--r--spec/javascripts/fixtures/new_branch.html.haml4
-rw-r--r--spec/javascripts/issue_spec.js.coffee86
-rw-r--r--spec/javascripts/new_branch_spec.js.coffee160
-rw-r--r--spec/lib/banzai/filter/user_reference_filter_spec.rb19
-rw-r--r--spec/lib/ci/gitlab_ci_yaml_processor_spec.rb6
-rw-r--r--spec/lib/gitlab/ldap/user_spec.rb15
-rw-r--r--spec/lib/gitlab/metrics/delta_spec.rb16
-rw-r--r--spec/lib/gitlab/metrics/instrumentation_spec.rb234
-rw-r--r--spec/lib/gitlab/metrics/metric_spec.rb54
-rw-r--r--spec/lib/gitlab/metrics/obfuscated_sql_spec.rb93
-rw-r--r--spec/lib/gitlab/metrics/rack_middleware_spec.rb63
-rw-r--r--spec/lib/gitlab/metrics/sampler_spec.rb97
-rw-r--r--spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb26
-rw-r--r--spec/lib/gitlab/metrics/subscribers/action_view_spec.rb37
-rw-r--r--spec/lib/gitlab/metrics/subscribers/active_record_spec.rb32
-rw-r--r--spec/lib/gitlab/metrics/system_spec.rb29
-rw-r--r--spec/lib/gitlab/metrics/transaction_spec.rb77
-rw-r--r--spec/lib/gitlab/metrics_spec.rb84
-rw-r--r--spec/lib/gitlab/reference_extractor_spec.rb10
-rw-r--r--spec/models/application_setting_spec.rb1
-rw-r--r--spec/models/build_spec.rb70
-rw-r--r--spec/models/ci/commit_spec.rb4
-rw-r--r--spec/models/concerns/issuable_spec.rb32
-rw-r--r--spec/models/concerns/mentionable_spec.rb17
-rw-r--r--spec/models/concerns/token_authenticatable_spec.rb23
-rw-r--r--spec/models/global_milestone_spec.rb10
-rw-r--r--spec/models/jira_issue_spec.rb30
-rw-r--r--spec/models/key_spec.rb2
-rw-r--r--spec/models/merge_request_spec.rb11
-rw-r--r--spec/models/note_spec.rb15
-rw-r--r--spec/models/project_services/hipchat_service_spec.rb22
-rw-r--r--spec/models/project_services/jira_service_spec.rb118
-rw-r--r--spec/models/project_services/slack_service/note_message_spec.rb8
-rw-r--r--spec/models/project_spec.rb30
-rw-r--r--spec/models/user_spec.rb5
-rw-r--r--spec/requests/api/branches_spec.rb2
-rw-r--r--spec/requests/api/merge_requests_spec.rb2
-rw-r--r--spec/requests/api/projects_spec.rb50
-rw-r--r--spec/requests/api/services_spec.rb2
-rw-r--r--spec/requests/api/users_spec.rb7
-rw-r--r--spec/requests/ci/api/runners_spec.rb1
-rw-r--r--spec/services/create_commit_builds_service_spec.rb39
-rw-r--r--spec/services/git_push_service_spec.rb69
-rw-r--r--spec/services/git_tag_push_service_spec.rb16
-rw-r--r--spec/services/notification_service_spec.rb3
-rw-r--r--spec/services/projects/update_service_spec.rb39
-rw-r--r--spec/services/system_note_service_spec.rb61
-rw-r--r--spec/services/update_snippet_service_spec.rb2
-rw-r--r--spec/support/jira_service_helper.rb67
-rw-r--r--spec/support/repo_helpers.rb12
-rw-r--r--spec/workers/repository_fork_worker_spec.rb20
-rw-r--r--spec/workers/stuck_ci_builds_worker_spec.rb4
403 files changed, 22333 insertions, 2136 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index a8da3de83f8..c23a7a3bf0e 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -12,6 +12,7 @@ before_script:
spec:feature:
script:
+ - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:feature
tags:
- ruby
diff --git a/.rubocop.yml b/.rubocop.yml
index b4ca11c8343..89aa0591c31 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -76,7 +76,7 @@ Style/BlockEndNewline:
Description: 'Put end statement of multiline block on its own line.'
Enabled: true
-Style/Blocks:
+Style/BlockDelimiters:
Description: >-
Avoid using {...} for multi-line blocks (multiline chaining is
always ugly).
@@ -232,6 +232,10 @@ Style/EvenOdd:
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods'
Enabled: false
+Style/ExtraSpacing:
+ Description: 'Do not use unnecessary spacing.'
+ Enabled: false
+
Style/FileName:
Description: 'Use snake_case for source file names.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-files'
@@ -431,6 +435,14 @@ Style/OpMethod:
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#other-arg'
Enabled: false
+Style/ParallelAssignment:
+ Description: >-
+ Check for simple usages of parallel assignment.
+ It will only warn when the number of variables
+ matches on both sides of the assignment.
+ StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parallel-assignment'
+ Enabled: false
+
Style/ParenthesesAroundCondition:
Description: >-
Don't use parentheses around the condition of an
@@ -669,6 +681,13 @@ Style/TrailingWhitespace:
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-whitespace'
Enabled: false
+Style/TrailingUnderscoreVariable:
+ Description: >-
+ Checks for the usage of unneeded trailing underscores at the
+ end of parallel variable assignment.
+ AllowNamedUnderscoreVariables: true
+ Enabled: false
+
Style/TrivialAccessors:
Description: 'Prefer attr_* methods to trivial readers/writers.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr_family'
@@ -690,11 +709,6 @@ Style/UnneededPercentQ:
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-q'
Enabled: false
-Style/UnneededPercentX:
- Description: 'Checks for %x when `` would do.'
- StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-x'
- Enabled: false
-
Style/VariableInterpolation:
Description: >-
Don't interpolate global, instance and class variables
@@ -778,6 +792,10 @@ Metrics/MethodLength:
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#short-methods'
Enabled: false
+Metrics/ModuleLength:
+ Description: 'Avoid modules longer than 100 lines of code.'
+ Enabled: false
+
#################### Lint ################################
### Warnings
@@ -961,6 +979,12 @@ Rails/ActionFilter:
Description: 'Enforces consistent use of action filter methods.'
Enabled: true
+Rails/Date:
+ Description: >-
+ Checks the correct usage of date aware methods,
+ such as Date.today, Date.current etc.
+ Enabled: false
+
Rails/DefaultScope:
Description: 'Checks if the argument passed to default_scope is a block.'
Enabled: false
@@ -987,6 +1011,12 @@ Rails/ScopeArgs:
Description: 'Checks the arguments of ActiveRecord scopes.'
Enabled: false
+Rails/TimeZone:
+ Description: 'Checks the correct usage of time zone aware methods.'
+ StyleGuide: 'https://github.com/bbatsov/rails-style-guide#time'
+ Reference: 'http://danilenko.org/2012/7/6/rails_timezones'
+ Enabled: false
+
Rails/Validation:
Description: 'Use validates :attribute, hash of validations.'
Enabled: false
diff --git a/CHANGELOG b/CHANGELOG
index 7f9dfd98cd7..ea403642e32 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,11 +1,43 @@
Please view this file on the master branch, on stable branches it's out of date.
-v 8.3.0 (unreleased)
+v 8.4.0 (unreleased)
+ - Implement new UI for group page
+ - Implement search inside emoji picker
+ - Add API support for looking up a user by username (Stan Hu)
+ - Add project permissions to all project API endpoints (Stan Hu)
+ - Only allow group/project members to mention `@all`
+ - Expose Git's version in the admin area (Trey Davis)
+ - Add "Frequently used" category to emoji picker
+ - Add CAS support (tduehr)
+ - Add link to merge request on build detail page
+ - Revert back upvote and downvote button to the issue and MR pages
+ - Swap position of Assignee and Author selector on Issuables (Zeger-Jan van de Weg)
+
+v 8.3.3 (unreleased)
+ - Fix project transfer e-mail sending incorrect paths in e-mail notification (Stan Hu)
+ - Enable "Add key" button when user fills in a proper key (Stan Hu)
+
+v 8.3.2
+ - Disable --follow in `git log` to avoid loading duplicate commit data in infinite scroll (Stan Hu)
+ - Add support for Google reCAPTCHA in user registration
+
+v 8.3.1
+ - Fix Error 500 when global milestones have slashes (Stan Hu)
+ - Fix Error 500 when doing a search in dashboard before visiting any project (Stan Hu)
+ - Fix LDAP identity and user retrieval when special characters are used
+ - Move Sidekiq-cron configuration to gitlab.yml
+ - Enable forcing Two-Factor authentication sitewide, with optional grace period
+
+v 8.3.0
+ - Bump rack-attack to 4.3.1 for security fix (Stan Hu)
+ - API support for starred projects for authorized user (Zeger-Jan van de Weg)
+ - Add open_issues_count to project API (Stan Hu)
- Expand character set of usernames created by Omniauth (Corey Hinshaw)
- Add button to automatically merge a merge request when the build succeeds (Zeger-Jan van de Weg)
- - Merge when build succeeds (Zeger-Jan van de Weg)
- Provide better diagnostic message upon project creation errors (Stan Hu)
- Bump devise to 3.5.3 to fix reset token expiring after account creation (Stan Hu)
+ - Remove api credentials from link to build_page
+ - Deprecate GitLabCiService making it to always be inactive
- Bump gollum-lib to 4.1.0 (Stan Hu)
- Fix broken group avatar upload under "New group" (Stan Hu)
- Update project repositorize size and commit count during import:repos task (Stan Hu)
@@ -17,13 +49,16 @@ v 8.3.0 (unreleased)
- Fix 500 error when update group member permission
- Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera)
- Recognize issue/MR/snippet/commit links as references
+ - Backport JIRA features from EE to CE
- Add ignore whitespace change option to commit view
- Fire update hook from GitLab
+ - Allow account unlock via email
- Style warning about mentioning many people in a comment
- Fix: sort milestones by due date once again (Greg Smethells)
- Migrate all CI::Services and CI::WebHooks to Services and WebHooks
- Don't show project fork event as "imported"
- Add API endpoint to fetch merge request commits list
+ - Don't create CI status for refs that doesn't have .gitlab-ci.yml, even if the builds are enabled
- Expose events API with comment information and author info
- Fix: Ensure "Remove Source Branch" button is not shown when branch is being deleted. #3583
- Run custom Git hooks when branch is created or deleted.
@@ -62,8 +97,6 @@ v 8.2.3
- Update documentation for "Guest" permissions
- Properly convert Emoji-only comments into Award Emojis
- Enable devise paranoid mode to prevent user enumeration attack
-
-v 8.2.3
- Webhook payload has an added, modified and removed properties for each commit
- Fix 500 error when creating a merge request that removes a submodule
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 7ced7c57889..b9c2b3d2f8e 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -155,6 +155,28 @@ sudo -u git -H bundle exec rake gitlab:env:info)
```
+### Issue weight
+
+Issue weight allows us to get an idea of the amount of work required to solve
+one or multiple issues. This makes it possible to schedule work more accurately.
+
+You are encouraged to set the weight of any issue. Following the guidelines
+below will make it easy to manage this, without unnecessary overhead.
+
+1. Set weight for any issue at the earliest possible convenience
+1. If you don't agree with a set weight, discuss with other developers until
+consensus is reached about the weight
+1. Issue weights are an abstract measurement of complexity of the issue. Do not
+relate issue weight directly to time. This is called [anchoring](https://en.wikipedia.org/wiki/Anchoring)
+and something you want to avoid.
+1. Something that has a weight of 1 (or no weight) is really small and simple.
+Something that is 9 is rewriting a large fundamental part of GitLab,
+which might lead to many hard problems to solve. Changing some text in GitLab
+is probably 1, adding a new Git Hook maybe 4 or 5, big features 7-9.
+1. If something is very large, it should probably be split up in multiple
+issues or chunks. You can simply not set the weight of a parent issue and set
+weights to children issues.
+
## Merge requests
We welcome merge requests with fixes and improvements to GitLab code, tests,
@@ -358,7 +380,7 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
[core team]: https://about.gitlab.com/core-team/
[getting help page]: https://about.gitlab.com/getting-help/
[Codetriage]: http://www.codetriage.com/gitlabhq/gitlabhq
-[up-for-grabs]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=up+for+grabs
+[up-for-grabs]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=up-for-grabs
[medium-up-for-grabs]: https://medium.com/@kentcdodds/first-timers-only-78281ea47455
[ce-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/issues
[ee-tracker]: https://gitlab.com/gitlab-org/gitlab-ee/issues
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 743af5e1251..d48d3702aed 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-2.6.8
+2.6.9
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index 2b7c5ae0184..4b9fcbec101 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-0.4.2
+0.5.1
diff --git a/Gemfile b/Gemfile
index 7298e21ce66..2a1c4f7d73a 100644
--- a/Gemfile
+++ b/Gemfile
@@ -23,6 +23,7 @@ gem 'devise-async', '~> 0.9.0'
gem 'doorkeeper', '~> 2.2.0'
gem 'omniauth', '~> 1.2.2'
gem 'omniauth-bitbucket', '~> 0.0.2'
+gem 'omniauth-cas3', '~> 1.1.2'
gem 'omniauth-facebook', '~> 3.0.0'
gem 'omniauth-github', '~> 1.1.1'
gem 'omniauth-gitlab', '~> 1.0.0'
@@ -34,6 +35,9 @@ gem 'omniauth-twitter', '~> 1.2.0'
gem 'omniauth_crowd'
gem 'rack-oauth2', '~> 1.2.1'
+# reCAPTCHA protection
+gem 'recaptcha', require: 'recaptcha/rails'
+
# Two-factor authentication
gem 'devise-two-factor', '~> 2.0.0'
gem 'rqrcode-rails3', '~> 0.1.7'
@@ -101,6 +105,9 @@ gem 'wikicloth', '0.8.1'
gem 'asciidoctor', '~> 1.5.2'
gem 'rouge', '~> 1.10.1'
+# See https://groups.google.com/forum/#!topic/ruby-security-ann/aSbgDiwb24s
+gem 'nokogiri', '1.6.7.1'
+
# Diffs
gem 'diffy', '~> 3.0.3'
@@ -168,14 +175,14 @@ gem 'd3_rails', '~> 3.5.5'
gem "cal-heatmap-rails", "~> 0.0.1"
# underscore-rails
-gem "underscore-rails", "~> 1.4.4"
+gem "underscore-rails", "~> 1.8.0"
# Sanitize user input
gem "sanitize", '~> 2.0'
gem 'babosa', '~> 1.0.2'
# Protect against bruteforcing
-gem "rack-attack", '~> 4.3.0'
+gem "rack-attack", '~> 4.3.1'
# Ace editor
gem 'ace-rails-ap', '~> 2.0.1'
@@ -186,7 +193,7 @@ gem 'mousetrap-rails', '~> 1.4.6'
# Detect and convert string character encoding
gem 'charlock_holmes', '~> 0.7.3'
-gem "sass-rails", '~> 4.0.5'
+gem "sass-rails", '~> 5.0.0'
gem "coffee-rails", '~> 4.1.0'
gem "uglifier", '~> 2.7.2'
gem 'turbolinks', '~> 2.5.0'
@@ -198,9 +205,9 @@ gem 'font-awesome-rails', '~> 4.2'
gem 'gitlab_emoji', '~> 0.2.0'
gem 'gon', '~> 6.0.1'
gem 'jquery-atwho-rails', '~> 1.3.2'
-gem 'jquery-rails', '~> 3.1.3'
+gem 'jquery-rails', '~> 4.0.0'
gem 'jquery-scrollto-rails', '~> 1.4.3'
-gem 'jquery-ui-rails', '~> 4.2.1'
+gem 'jquery-ui-rails', '~> 5.0.0'
gem 'nprogress-rails', '~> 0.1.6.7'
gem 'raphael-rails', '~> 2.1.2'
gem 'request_store', '~> 1.2.0'
@@ -208,14 +215,22 @@ gem 'select2-rails', '~> 3.5.9'
gem 'virtus', '~> 1.0.1'
gem 'net-ssh', '~> 3.0.1'
+# Metrics
+group :metrics do
+ gem 'allocations', '~> 1.0', require: false, platform: :mri
+ gem 'method_source', '~> 0.8', require: false
+ gem 'influxdb', '~> 0.2', require: false
+ gem 'connection_pool', '~> 2.0', require: false
+end
+
group :development do
gem "foreman"
- gem 'brakeman', '3.0.1', require: false
+ gem 'brakeman', '~> 3.1.0', require: false
gem "annotate", "~> 2.6.0"
gem "letter_opener", '~> 1.1.2'
gem 'quiet_assets', '~> 1.0.2'
- gem 'rerun', '~> 0.10.0'
+ gem 'rerun', '~> 0.11.0'
gem 'bullet', require: false
gem 'rblineprof', platform: :mri, require: false
gem 'web-console', '~> 2.0'
@@ -251,7 +266,7 @@ group :development, :test do
gem 'capybara', '~> 2.4.0'
gem 'capybara-screenshot', '~> 1.0.0'
- gem 'poltergeist', '~> 1.6.0'
+ gem 'poltergeist', '~> 1.8.1'
gem 'teaspoon', '~> 1.0.0'
gem 'teaspoon-jasmine', '~> 2.2.0'
@@ -261,7 +276,7 @@ group :development, :test do
gem 'spring-commands-spinach', '~> 1.0.0'
gem 'spring-commands-teaspoon', '~> 0.0.2'
- gem 'rubocop', '~> 0.28.0', require: false
+ gem 'rubocop', '~> 0.35.0', require: false
gem 'coveralls', '~> 0.8.2', require: false
gem 'simplecov', '~> 0.10.0', require: false
gem 'flog', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index ff57460f5bb..9769ae80a7d 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -49,6 +49,7 @@ GEM
addressable (2.3.8)
after_commit_queue (1.3.0)
activerecord (>= 3.0)
+ allocations (1.0.3)
annotate (2.6.10)
activerecord (>= 3.2, <= 4.3)
rake (~> 10.4)
@@ -65,7 +66,7 @@ GEM
attr_encrypted (1.3.4)
encryptor (>= 1.3.0)
attr_required (1.0.0)
- autoprefixer-rails (6.1.1)
+ autoprefixer-rails (6.1.2)
execjs
json
awesome_print (1.2.0)
@@ -84,15 +85,17 @@ GEM
bootstrap-sass (3.3.5)
autoprefixer-rails (>= 5.0.0.1)
sass (>= 3.2.19)
- brakeman (3.0.1)
+ brakeman (3.1.4)
erubis (~> 2.6)
fastercsv (~> 1.5)
haml (>= 3.0, < 5.0)
- highline (~> 1.6.20)
+ highline (>= 1.6.20, < 2.0)
multi_json (~> 1.2)
- ruby2ruby (~> 2.1.1)
- ruby_parser (~> 3.5.0)
+ ruby2ruby (>= 2.1.1, < 2.3.0)
+ ruby_parser (~> 3.7.0)
+ safe_yaml (>= 1.0)
sass (~> 3.0)
+ slim (>= 1.3.6, < 4.0)
terminal-table (~> 1.4)
browser (1.0.1)
builder (3.2.2)
@@ -102,7 +105,7 @@ GEM
bundler-audit (0.4.0)
bundler (~> 1.2)
thor (~> 0.18)
- byebug (8.2.0)
+ byebug (8.2.1)
cal-heatmap-rails (0.0.1)
capybara (2.4.4)
mime-types (>= 1.16)
@@ -117,23 +120,7 @@ GEM
activemodel (>= 3.2.0)
activesupport (>= 3.2.0)
json (>= 1.7)
- celluloid (0.17.2)
- celluloid-essentials
- celluloid-extras
- celluloid-fsm
- celluloid-pool
- celluloid-supervision
- timers (>= 4.1.1)
- celluloid-essentials (0.20.5)
- timers (>= 4.1.1)
- celluloid-extras (0.20.5)
- timers (>= 4.1.1)
- celluloid-fsm (0.20.5)
- timers (>= 4.1.1)
- celluloid-pool (0.20.5)
- timers (>= 4.1.1)
- celluloid-supervision (0.20.5)
- timers (>= 4.1.1)
+ cause (0.1)
charlock_holmes (0.7.3)
chunky_png (1.3.5)
cliver (0.3.2)
@@ -157,10 +144,10 @@ GEM
term-ansicolor (~> 1.3)
thor (~> 0.19.1)
tins (~> 1.6.0)
- crack (0.4.2)
+ crack (0.4.3)
safe_yaml (~> 1.0.0)
creole (0.5.0)
- d3_rails (3.5.6)
+ d3_rails (3.5.11)
railties (>= 3.1.0)
daemons (1.2.3)
database_cleaner (1.4.1)
@@ -247,7 +234,7 @@ GEM
ipaddress (~> 0.5)
nokogiri (~> 1.5, >= 1.5.11)
opennebula
- fog-brightbox (0.9.0)
+ fog-brightbox (0.10.1)
fog-core (~> 1.22)
fog-json
inflecto (~> 0.0.2)
@@ -266,7 +253,7 @@ GEM
fog-core (>= 1.21.0)
fog-json
fog-xml (>= 0.0.1)
- fog-sakuracloud (1.4.0)
+ fog-sakuracloud (1.5.0)
fog-core
fog-json
fog-softlayer (1.0.2)
@@ -294,11 +281,11 @@ GEM
ruby-progressbar (~> 1.4)
gemnasium-gitlab-service (0.2.6)
rugged (~> 0.21)
- gemojione (2.1.0)
+ gemojione (2.1.1)
json
get_process_mem (0.2.0)
gherkin-ruby (0.3.2)
- github-linguist (4.7.2)
+ github-linguist (4.7.3)
charlock_holmes (~> 0.7.3)
escape_utils (~> 1.1.0)
mime-types (>= 1.19)
@@ -315,7 +302,7 @@ GEM
posix-spawn (~> 0.3)
gitlab_emoji (0.2.0)
gemojione (~> 2.1)
- gitlab_git (7.2.21)
+ gitlab_git (7.2.22)
activesupport (~> 4.0)
charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0)
@@ -364,12 +351,11 @@ GEM
html2haml (>= 1.0.1)
railties (>= 4.0.1)
hashie (3.4.3)
- highline (1.6.21)
+ highline (1.7.8)
hike (1.2.3)
hipchat (1.5.2)
httparty
mimemagic
- hitimes (1.2.3)
html-pipeline (1.11.0)
activesupport (>= 2)
nokogiri (~> 1.4)
@@ -388,17 +374,21 @@ GEM
i18n (0.7.0)
ice_nine (0.11.1)
inflecto (0.0.2)
+ influxdb (0.2.3)
+ cause
+ json
ipaddress (0.8.0)
jquery-atwho-rails (1.3.2)
- jquery-rails (3.1.4)
- railties (>= 3.0, < 5.0)
+ jquery-rails (4.0.5)
+ rails-dom-testing (~> 1.0)
+ railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
jquery-scrollto-rails (1.4.3)
railties (> 3.1, < 5.0)
jquery-turbolinks (2.1.0)
railties (>= 3.1.0)
turbolinks
- jquery-ui-rails (4.2.1)
+ jquery-ui-rails (5.0.5)
railties (>= 3.2.16)
json (1.8.3)
jwt (1.5.2)
@@ -410,8 +400,7 @@ GEM
addressable (~> 2.3)
letter_opener (1.1.2)
launchy (~> 2.2)
- listen (2.9.0)
- celluloid (>= 0.15.2)
+ listen (3.0.5)
rb-fsevent (>= 0.9.3)
rb-inotify (>= 0.9)
loofah (2.0.3)
@@ -424,7 +413,7 @@ GEM
method_source (0.8.2)
mime-types (1.25.1)
mimemagic (0.3.0)
- mini_portile (0.6.2)
+ mini_portile2 (2.0.0)
minitest (5.7.0)
mousetrap-rails (1.4.6)
multi_json (1.11.2)
@@ -435,12 +424,12 @@ GEM
net-ldap (0.12.1)
net-ssh (3.0.1)
netrc (0.11.0)
- newrelic-grape (2.0.0)
+ newrelic-grape (2.1.0)
grape
newrelic_rpm
newrelic_rpm (3.9.4.245)
- nokogiri (1.6.6.4)
- mini_portile (~> 0.6.0)
+ nokogiri (1.6.7.1)
+ mini_portile2 (~> 2.0.0.rc2)
nprogress-rails (0.1.6.7)
oauth (0.4.7)
oauth2 (1.0.0)
@@ -458,6 +447,10 @@ GEM
multi_json (~> 1.7)
omniauth (~> 1.1)
omniauth-oauth (~> 1.0)
+ omniauth-cas3 (1.1.3)
+ addressable (~> 2.3)
+ nokogiri (~> 1.6.6)
+ omniauth (~> 1.2)
omniauth-facebook (3.0.0)
omniauth-oauth2 (~> 1.2)
omniauth-github (1.1.2)
@@ -507,13 +500,13 @@ GEM
parser (2.2.3.0)
ast (>= 1.1, < 3.0)
pg (0.18.4)
- poltergeist (1.6.0)
+ poltergeist (1.8.1)
capybara (~> 2.1)
cliver (~> 0.3.1)
multi_json (~> 1.0)
websocket-driver (>= 0.2.0)
posix-spawn (0.3.11)
- powerpack (0.0.9)
+ powerpack (0.1.1)
pry (0.10.3)
coderay (~> 1.1.0)
method_source (~> 0.8.1)
@@ -526,7 +519,7 @@ GEM
rack (1.6.4)
rack-accept (0.4.5)
rack (>= 0.4)
- rack-attack (4.3.0)
+ rack-attack (4.3.1)
rack
rack-cors (0.4.0)
rack-mount (0.8.3)
@@ -580,6 +573,8 @@ GEM
trollop
rdoc (3.12.2)
json (~> 1.4)
+ recaptcha (1.0.2)
+ json
redcarpet (3.3.3)
redis (3.2.2)
redis-actionpack (4.0.1)
@@ -601,8 +596,8 @@ GEM
redis-store (1.1.7)
redis (>= 2.2)
request_store (1.2.1)
- rerun (0.10.0)
- listen (~> 2.7, >= 2.7.3)
+ rerun (0.11.0)
+ listen (~> 3.0)
responders (2.1.0)
railties (>= 4.2.0, < 5)
rest-client (1.8.0)
@@ -637,22 +632,23 @@ GEM
rspec-mocks (~> 3.3.0)
rspec-support (~> 3.3.0)
rspec-support (3.3.0)
- rubocop (0.28.0)
+ rubocop (0.35.1)
astrolabe (~> 1.3)
- parser (>= 2.2.0.pre.7, < 3.0)
- powerpack (~> 0.0.6)
+ parser (>= 2.2.3.0, < 3.0)
+ powerpack (~> 0.1)
rainbow (>= 1.99.1, < 3.0)
- ruby-progressbar (~> 1.4)
+ ruby-progressbar (~> 1.7)
+ tins (<= 1.6.0)
ruby-fogbugz (0.2.1)
crack (~> 0.4)
ruby-progressbar (1.7.5)
ruby-saml (1.0.0)
nokogiri (>= 1.5.10)
uuid (~> 2.3)
- ruby2ruby (2.1.4)
+ ruby2ruby (2.2.0)
ruby_parser (~> 3.1)
sexp_processor (~> 4.0)
- ruby_parser (3.5.0)
+ ruby_parser (3.7.2)
sexp_processor (~> 4.1)
rubyntlm (0.5.2)
rubypants (0.2.0)
@@ -661,12 +657,13 @@ GEM
safe_yaml (1.0.4)
sanitize (2.1.0)
nokogiri (>= 1.4.4)
- sass (3.2.19)
- sass-rails (4.0.5)
+ sass (3.4.20)
+ sass-rails (5.0.4)
railties (>= 4.0.0, < 5.0)
- sass (~> 3.2.2)
- sprockets (~> 2.8, < 3.0)
- sprockets-rails (~> 2.0)
+ sass (~> 3.1)
+ sprockets (>= 2.8, < 4.0)
+ sprockets-rails (>= 2.0, < 4.0)
+ tilt (>= 1.1, < 3)
sawyer (0.6.0)
addressable (~> 2.3.5)
faraday (~> 0.8, < 0.10)
@@ -705,6 +702,9 @@ GEM
tilt (>= 1.3, < 3)
six (0.2.0)
slack-notifier (1.2.1)
+ slim (3.0.6)
+ temple (~> 0.7.3)
+ tilt (>= 1.3.3, < 2.1)
slop (3.6.0)
spinach (0.8.10)
colorize
@@ -746,6 +746,7 @@ GEM
railties (>= 3.2.5, < 5)
teaspoon-jasmine (2.2.0)
teaspoon (>= 1.0.0)
+ temple (0.7.6)
term-ansicolor (1.3.2)
tins (~> 1.0)
terminal-table (1.5.2)
@@ -758,8 +759,6 @@ GEM
thor (0.19.1)
thread_safe (0.3.5)
tilt (1.4.1)
- timers (4.1.1)
- hitimes
timfel-krb5-auth (0.8.3)
tinder (1.10.1)
eventmachine (~> 1.0)
@@ -783,7 +782,7 @@ GEM
uglifier (2.7.2)
execjs (>= 0.3.0)
json (>= 1.8.0)
- underscore-rails (1.4.4)
+ underscore-rails (1.8.3)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.1)
@@ -803,7 +802,7 @@ GEM
coercible (~> 1.0)
descendants_tracker (~> 0.0, >= 0.0.3)
equalizer (~> 0.0, >= 0.0.9)
- warden (1.2.3)
+ warden (1.2.4)
rack (>= 1.0)
web-console (2.2.1)
activemodel (>= 4.0)
@@ -834,6 +833,7 @@ DEPENDENCIES
acts-as-taggable-on (~> 3.4)
addressable (~> 2.3.8)
after_commit_queue
+ allocations (~> 1.0)
annotate (~> 2.6.0)
asana (~> 0.4.0)
asciidoctor (~> 1.5.2)
@@ -844,7 +844,7 @@ DEPENDENCIES
better_errors (~> 1.0.1)
binding_of_caller (~> 0.7.2)
bootstrap-sass (~> 3.0)
- brakeman (= 3.0.1)
+ brakeman (~> 3.1.0)
browser (~> 1.0.0)
bullet
bundler-audit
@@ -856,6 +856,7 @@ DEPENDENCIES
charlock_holmes (~> 0.7.3)
coffee-rails (~> 4.1.0)
colorize (~> 0.7.0)
+ connection_pool (~> 2.0)
coveralls (~> 0.8.2)
creole (~> 0.5.0)
d3_rails (~> 3.5.5)
@@ -893,14 +894,16 @@ DEPENDENCIES
hipchat (~> 1.5.0)
html-pipeline (~> 1.11.0)
httparty (~> 0.13.3)
+ influxdb (~> 0.2)
jquery-atwho-rails (~> 1.3.2)
- jquery-rails (~> 3.1.3)
+ jquery-rails (~> 4.0.0)
jquery-scrollto-rails (~> 1.4.3)
jquery-turbolinks (~> 2.1.0)
- jquery-ui-rails (~> 4.2.1)
+ jquery-ui-rails (~> 5.0.0)
kaminari (~> 0.16.3)
letter_opener (~> 1.1.2)
mail_room (~> 0.6.1)
+ method_source (~> 0.8)
minitest (~> 5.7.0)
mousetrap-rails (~> 1.4.6)
mysql2 (~> 0.3.16)
@@ -908,11 +911,13 @@ DEPENDENCIES
net-ssh (~> 3.0.1)
newrelic-grape
newrelic_rpm (~> 3.9.4.245)
+ nokogiri (= 1.6.7.1)
nprogress-rails (~> 0.1.6.7)
oauth2 (~> 1.0.0)
octokit (~> 3.7.0)
omniauth (~> 1.2.2)
omniauth-bitbucket (~> 0.0.2)
+ omniauth-cas3 (~> 1.1.2)
omniauth-facebook (~> 3.0.0)
omniauth-github (~> 1.1.1)
omniauth-gitlab (~> 1.0.0)
@@ -925,10 +930,10 @@ DEPENDENCIES
org-ruby (~> 0.9.12)
paranoia (~> 2.0)
pg (~> 0.18.2)
- poltergeist (~> 1.6.0)
+ poltergeist (~> 1.8.1)
pry-rails
quiet_assets (~> 1.0.2)
- rack-attack (~> 4.3.0)
+ rack-attack (~> 4.3.1)
rack-cors (~> 0.4.0)
rack-oauth2 (~> 1.2.1)
rails (= 4.2.4)
@@ -936,19 +941,20 @@ DEPENDENCIES
raphael-rails (~> 2.1.2)
rblineprof
rdoc (~> 3.6)
+ recaptcha
redcarpet (~> 3.3.3)
redis-namespace
redis-rails (~> 4.0.0)
request_store (~> 1.2.0)
- rerun (~> 0.10.0)
+ rerun (~> 0.11.0)
responders (~> 2.0)
rouge (~> 1.10.1)
rqrcode-rails3 (~> 0.1.7)
rspec-rails (~> 3.3.0)
- rubocop (~> 0.28.0)
+ rubocop (~> 0.35.0)
ruby-fogbugz (~> 0.2.1)
sanitize (~> 2.0)
- sass-rails (~> 4.0.5)
+ sass-rails (~> 5.0.0)
sdoc (~> 0.3.20)
seed-fu (~> 2.3.5)
select2-rails (~> 3.5.9)
@@ -977,7 +983,7 @@ DEPENDENCIES
tinder (~> 1.10.0)
turbolinks (~> 2.5.0)
uglifier (~> 2.7.2)
- underscore-rails (~> 1.4.4)
+ underscore-rails (~> 1.8.0)
unf (~> 0.1.4)
unicorn (~> 4.8.2)
unicorn-worker-killer (~> 0.4.2)
diff --git a/VERSION b/VERSION
index 8d0676ff07b..ce669730119 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-8.3.0.pre
+8.4.0.pre
diff --git a/app/assets/images/brand_logo.png b/app/assets/images/brand_logo.png
deleted file mode 100644
index 9c564bb6141..00000000000
--- a/app/assets/images/brand_logo.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/emoji.png b/app/assets/images/emoji.png
new file mode 100644
index 00000000000..a8ad7b6eab6
--- /dev/null
+++ b/app/assets/images/emoji.png
Binary files differ
diff --git a/app/assets/images/gitlab_logo.png b/app/assets/images/gitlab_logo.png
new file mode 100644
index 00000000000..0c157546b9c
--- /dev/null
+++ b/app/assets/images/gitlab_logo.png
Binary files differ
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index 1539eba0faa..affab5bb030 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -5,7 +5,7 @@
# the compiled file.
#
#= require jquery
-#= require jquery.ui.all
+#= require jquery-ui
#= require jquery_ujs
#= require jquery.cookie
#= require jquery.endless-scroll
diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee
index 3ff9ba77dfc..619abb1fb07 100644
--- a/app/assets/javascripts/awards_handler.coffee
+++ b/app/assets/javascripts/awards_handler.coffee
@@ -1,12 +1,28 @@
class @AwardsHandler
constructor: (@post_emoji_url, @noteable_type, @noteable_id, @aliases) ->
+ $(".add-award").click (event)->
+ event.stopPropagation()
+ event.preventDefault()
+ $(".emoji-menu").show()
+
+ $("html").click ->
+ if !$(event.target).closest(".emoji-menu").length
+ if $(".emoji-menu").is(":visible")
+ $(".emoji-menu").hide()
+
+ @renderFrequentlyUsedBlock()
+ @setupSearch()
addAward: (emoji) ->
emoji = @normilizeEmojiName(emoji)
@postEmoji emoji, =>
@addAwardToEmojiBar(emoji)
+
+ $(".emoji-menu").hide()
- addAwardToEmojiBar: (emoji, custom_path = '') ->
+ addAwardToEmojiBar: (emoji) ->
+ @addEmojiToFrequentlyUsedList(emoji)
+
emoji = @normilizeEmojiName(emoji)
if @exist(emoji)
if @isActive(emoji)
@@ -17,7 +33,7 @@ class @AwardsHandler
counter.parent().addClass("active")
@addMeToAuthorList(emoji)
else
- @createEmoji(emoji, custom_path)
+ @createEmoji(emoji)
exist: (emoji) ->
@findEmojiIcon(emoji).length > 0
@@ -27,15 +43,19 @@ class @AwardsHandler
decrementCounter: (emoji) ->
counter = @findEmojiIcon(emoji).siblings(".counter")
+ emojiIcon = counter.parent()
if parseInt(counter.text()) > 1
counter.text(parseInt(counter.text()) - 1)
- counter.parent().removeClass("active")
+ emojiIcon.removeClass("active")
@removeMeFromAuthorList(emoji)
+ else if emoji =="thumbsup" || emoji == "thumbsdown"
+ emojiIcon.tooltip("destroy")
+ counter.text(0)
+ emojiIcon.removeClass("active")
else
- award = counter.parent()
- award.tooltip("destroy")
- award.remove()
+ emojiIcon.tooltip("destroy")
+ emojiIcon.remove()
removeMeFromAuthorList: (emoji) ->
award_block = @findEmojiIcon(emoji).parent()
@@ -54,35 +74,39 @@ class @AwardsHandler
resetTooltip: (award) ->
award.tooltip("destroy")
- # "destroy" call is asynchronous, this is why we need to set timeout.
+ # "destroy" call is asynchronous and there is no appropriate callback on it, this is why we need to set timeout.
setTimeout (->
award.tooltip()
), 200
- createEmoji: (emoji, custom_path) ->
+ createEmoji: (emoji) ->
+ emojiCssClass = @resolveNameToCssClass(emoji)
+
nodes = []
nodes.push("<div class='award active' title='me'>")
- nodes.push("<div class='icon' data-emoji='" + emoji + "'>")
- nodes.push(@getImage(emoji, custom_path))
+ nodes.push("<div class='icon emoji-icon #{emojiCssClass}' data-emoji='#{emoji}'></div>")
+ nodes.push("<div class='counter'>1</div>")
nodes.push("</div>")
- nodes.push("<div class='counter'>1")
- nodes.push("</div></div>")
- $(".awards-controls").before(nodes.join("\n"))
+ emoji_node = $(nodes.join("\n")).insertBefore(".awards-controls").find(".emoji-icon").data("emoji", emoji)
$(".award").tooltip()
- getImage: (emoji, custom_path) ->
- if custom_path
- $("<img>").attr({src: custom_path, width: 20, height: 20}).wrap("<div>").parent().html()
+ resolveNameToCssClass: (emoji) ->
+ emoji_icon = $(".emoji-menu-content [data-emoji='#{emoji}']")
+
+ if emoji_icon.length > 0
+ unicodeName = emoji_icon.data("unicode-name")
else
- $("li[data-emoji='" + emoji + "']").html()
+ # Find by alias
+ unicodeName = $(".emoji-menu-content [data-aliases*=':#{emoji}:']").data("unicode-name")
+ "emoji-#{unicodeName}"
postEmoji: (emoji, callback) ->
$.post @post_emoji_url, { note: {
- note: ":" + emoji + ":"
+ note: ":#{emoji}:"
noteable_type: @noteable_type
noteable_id: @noteable_id
}},(data) ->
@@ -90,7 +114,7 @@ class @AwardsHandler
callback.call()
findEmojiIcon: (emoji) ->
- $(".icon[data-emoji='" + emoji + "']")
+ $(".award [data-emoji='#{emoji}']")
scrollToAwards: ->
$('body, html').animate({
@@ -99,3 +123,44 @@ class @AwardsHandler
normilizeEmojiName: (emoji) ->
@aliases[emoji] || emoji
+
+ addEmojiToFrequentlyUsedList: (emoji) ->
+ frequently_used_emojis = @getFrequentlyUsedEmojis()
+ frequently_used_emojis.push(emoji)
+ $.cookie('frequently_used_emojis', frequently_used_emojis.join(","), { expires: 365 })
+
+ getFrequentlyUsedEmojis: ->
+ frequently_used_emojis = ($.cookie('frequently_used_emojis') || "").split(",")
+ _.compact(_.uniq(frequently_used_emojis))
+
+ renderFrequentlyUsedBlock: ->
+ if $.cookie('frequently_used_emojis')
+ frequently_used_emojis = @getFrequentlyUsedEmojis()
+
+ ul = $("<ul>")
+
+ for emoji in frequently_used_emojis
+ do (emoji) ->
+ $(".emoji-menu-content [data-emoji='#{emoji}']").closest("li").clone().appendTo(ul)
+
+ $("input.emoji-search").after(ul).after($("<h5>").text("Frequently used"))
+
+ setupSearch: ->
+ $("input.emoji-search").keyup (ev) =>
+ term = $(ev.target).val()
+
+ # Clean previous search results
+ $("ul.emoji-search,h5.emoji-search").remove()
+
+ if term
+ # Generate a search result block
+ h5 = $("<h5>").text("Search results").addClass("emoji-search")
+ found_emojis = @searchEmojis(term).show()
+ ul = $("<ul>").addClass("emoji-search").append(found_emojis)
+ $(".emoji-menu-content ul, .emoji-menu-content h5").hide()
+ $(".emoji-menu-content").append(h5).append(ul)
+ else
+ $(".emoji-menu-content").children().show()
+
+ searchEmojis: (term)->
+ $(".emoji-menu-content [data-emoji*='#{term}']").closest("li").clone()
diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js.coffee b/app/assets/javascripts/blob/blob_file_dropzone.js.coffee
index 195f8b11e5d..9df932817f6 100644
--- a/app/assets/javascripts/blob/blob_file_dropzone.js.coffee
+++ b/app/assets/javascripts/blob/blob_file_dropzone.js.coffee
@@ -35,7 +35,7 @@ class @BlobFileDropzone
return
this.on 'sending', (file, xhr, formData) ->
- formData.append('new_branch', form.find('.js-new-branch').val())
+ formData.append('target_branch', form.find('.js-target-branch').val())
formData.append('create_merge_request', form.find('.js-create-merge-request').val())
formData.append('commit_message', form.find('.js-commit-message').val())
return
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index 599b4c49540..69e061ce6e9 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -49,7 +49,7 @@ class Dispatcher
new DropzoneInput($('.release-form'))
when 'projects:merge_requests:show'
new Diff()
- shortcut_handler = new ShortcutsIssuable()
+ shortcut_handler = new ShortcutsIssuable(true)
new ZenMode()
when "projects:merge_requests:diffs"
new Diff()
diff --git a/app/assets/javascripts/issuable_context.js.coffee b/app/assets/javascripts/issuable_context.js.coffee
index 01bd515cc02..02232698bc2 100644
--- a/app/assets/javascripts/issuable_context.js.coffee
+++ b/app/assets/javascripts/issuable_context.js.coffee
@@ -18,7 +18,7 @@ class @IssuableContext
$('.issuable-affix').affix offset:
top: ->
- @top = ($('.issuable-affix').offset().top - 60)
+ @top = ($('.issuable-affix').offset().top - 70)
bottom: ->
@bottom = $('.footer').outerHeight(true)
diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee
index 603a16da1ce..c256ec8f41b 100644
--- a/app/assets/javascripts/issue.js.coffee
+++ b/app/assets/javascripts/issue.js.coffee
@@ -1,3 +1,4 @@
+#= require flash
#= require jquery.waitforimages
#= require task_list
@@ -6,16 +7,47 @@ class @Issue
# Prevent duplicate event bindings
@disableTaskList()
- if $("a.btn-close").length
+ if $('a.btn-close').length
@initTaskList()
+ @initIssueBtnEventListeners()
initTaskList: ->
- $('.issue-details .js-task-list-container').taskList('enable')
- $(document).on 'tasklist:changed', '.issue-details .js-task-list-container', @updateTaskList
+ $('.detail-page-description .js-task-list-container').taskList('enable')
+ $(document).on 'tasklist:changed', '.detail-page-description .js-task-list-container', @updateTaskList
+
+ initIssueBtnEventListeners: ->
+ issueFailMessage = 'Unable to update this issue at this time.'
+ $('a.btn-close, a.btn-reopen').on 'click', (e) ->
+ e.preventDefault()
+ e.stopImmediatePropagation()
+ $this = $(this)
+ isClose = $this.hasClass('btn-close')
+ $this.prop('disabled', true)
+ url = $this.attr('href')
+ $.ajax
+ type: 'PUT'
+ url: url,
+ error: (jqXHR, textStatus, errorThrown) ->
+ issueStatus = if isClose then 'close' else 'open'
+ new Flash(issueFailMessage, 'alert')
+ success: (data, textStatus, jqXHR) ->
+ if data.saved
+ $this.addClass('hidden')
+ if isClose
+ $('a.btn-reopen').removeClass('hidden')
+ $('div.status-box-closed').removeClass('hidden')
+ $('div.status-box-open').addClass('hidden')
+ else
+ $('a.btn-close').removeClass('hidden')
+ $('div.status-box-closed').addClass('hidden')
+ $('div.status-box-open').removeClass('hidden')
+ else
+ new Flash(issueFailMessage, 'alert')
+ $this.prop('disabled', false)
disableTaskList: ->
- $('.issue-details .js-task-list-container').taskList('disable')
- $(document).off 'tasklist:changed', '.issue-details .js-task-list-container'
+ $('.detail-page-description .js-task-list-container').taskList('disable')
+ $(document).off 'tasklist:changed', '.detail-page-description .js-task-list-container'
# TODO (rspeicher): Make the issue description inline-editable like a note so
# that we can re-use its form here
diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee
index b21cb7904b5..9047587db81 100644
--- a/app/assets/javascripts/merge_request.js.coffee
+++ b/app/assets/javascripts/merge_request.js.coffee
@@ -40,12 +40,12 @@ class @MergeRequest
this.$('.all-commits').removeClass 'hide'
initTaskList: ->
- $('.merge-request-details .js-task-list-container').taskList('enable')
- $(document).on 'tasklist:changed', '.merge-request-details .js-task-list-container', @updateTaskList
+ $('.detail-page-description .js-task-list-container').taskList('enable')
+ $(document).on 'tasklist:changed', '.detail-page-description .js-task-list-container', @updateTaskList
disableTaskList: ->
- $('.merge-request-details .js-task-list-container').taskList('disable')
- $(document).off 'tasklist:changed', '.merge-request-details .js-task-list-container'
+ $('.detail-page-description .js-task-list-container').taskList('disable')
+ $(document).off 'tasklist:changed', '.detail-page-description .js-task-list-container'
# TODO (rspeicher): Make the merge request description inline-editable like a
# note so that we can re-use its form here
diff --git a/app/assets/javascripts/merge_request_widget.js.coffee b/app/assets/javascripts/merge_request_widget.js.coffee
index c4b63966fe7..738ffc8343b 100644
--- a/app/assets/javascripts/merge_request_widget.js.coffee
+++ b/app/assets/javascripts/merge_request_widget.js.coffee
@@ -18,7 +18,7 @@ class @MergeRequestWidget
if data.state == "merged"
urlSuffix = if deleteSourceBranch then '?delete_source=true' else ''
- window.location.href = window.location.href + urlSuffix
+ window.location.href = window.location.pathname + urlSuffix
else if data.merge_error
$('.mr-widget-body').html("<h4>" + data.merge_error + "</h4>")
else
diff --git a/app/assets/javascripts/new_branch_form.js.coffee b/app/assets/javascripts/new_branch_form.js.coffee
new file mode 100644
index 00000000000..4b350854f78
--- /dev/null
+++ b/app/assets/javascripts/new_branch_form.js.coffee
@@ -0,0 +1,78 @@
+class @NewBranchForm
+ constructor: (form, availableRefs) ->
+ @branchNameError = form.find('.js-branch-name-error')
+ @name = form.find('.js-branch-name')
+ @ref = form.find('#ref')
+
+ @setupAvailableRefs(availableRefs)
+ @setupRestrictions()
+ @addBinding()
+ @init()
+
+ addBinding: ->
+ @name.on 'blur', @validate
+
+ init: ->
+ @name.trigger 'blur' if @name.val().length > 0
+
+ setupAvailableRefs: (availableRefs) ->
+ @ref.autocomplete
+ source: availableRefs,
+ minLength: 1
+
+ setupRestrictions: ->
+ startsWith = {
+ pattern: /^(\/|\.)/g,
+ prefix: "can't start with",
+ conjunction: "or"
+ }
+
+ endsWith = {
+ pattern: /(\/|\.|\.lock)$/g,
+ prefix: "can't end in",
+ conjunction: "or"
+ }
+
+ invalid = {
+ pattern: /(\s|~|\^|:|\?|\*|\[|\\|\.\.|@\{|\/{2,}){1}/g
+ prefix: "can't contain",
+ conjunction: ", "
+ }
+
+ single = {
+ pattern: /^@+$/g
+ prefix: "can't be",
+ conjunction: "or"
+ }
+
+ @restrictions = [startsWith, invalid, endsWith, single]
+
+ validate: =>
+ @branchNameError.empty()
+
+ unique = (values, value) ->
+ values.push(value) unless value in values
+ values
+
+ formatter = (values, restriction) ->
+ formatted = values.map (value) ->
+ switch
+ when /\s/.test value then 'spaces'
+ when /\/{2,}/g.test value then 'consecutive slashes'
+ else "'#{value}'"
+
+ "#{restriction.prefix} #{formatted.join(restriction.conjunction)}"
+
+ validator = (errors, restriction) =>
+ matched = @name.val().match(restriction.pattern)
+
+ if matched
+ errors.concat formatter(matched.reduce(unique, []), restriction)
+ else
+ errors
+
+ errors = @restrictions.reduce validator, []
+
+ if errors.length > 0
+ errorMessage = $("<span/>").text(errors.join(', '))
+ @branchNameError.append(errorMessage)
diff --git a/app/assets/javascripts/new_commit_form.js.coffee b/app/assets/javascripts/new_commit_form.js.coffee
index 3c7b776155f..03f0f51acfa 100644
--- a/app/assets/javascripts/new_commit_form.js.coffee
+++ b/app/assets/javascripts/new_commit_form.js.coffee
@@ -1,6 +1,6 @@
class @NewCommitForm
constructor: (form) ->
- @newBranch = form.find('.js-new-branch')
+ @newBranch = form.find('.js-target-branch')
@originalBranch = form.find('.js-original-branch')
@createMergeRequest = form.find('.js-create-merge-request')
@createMergeRequestContainer = form.find('.js-create-merge-request-container')
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index 35dc7829da2..9e5204bfeeb 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -127,7 +127,7 @@ class @Notes
@initTaskList()
if note.award
- awards_handler.addAwardToEmojiBar(note.note, note.emoji_path)
+ awards_handler.addAwardToEmojiBar(note.note)
awards_handler.scrollToAwards()
###
diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee
index 1f221945c06..d7a658f8faa 100644
--- a/app/assets/javascripts/project.js.coffee
+++ b/app/assets/javascripts/project.js.coffee
@@ -1,7 +1,7 @@
class @Project
constructor: ->
# Git protocol switcher
- $('.js-protocol-switch').click ->
+ $('ul.clone-options-dropdown a').click ->
return if $(@).hasClass('active')
@@ -10,7 +10,8 @@ class @Project
# Add the active class for the clicked button
$(@).toggleClass('active')
- url = $(@).data('clone')
+ url = $("#project_clone").val()
+ console.log("url",url)
# Update the input field
$('#project_clone').val(url)
diff --git a/app/assets/javascripts/projects_list.js.coffee b/app/assets/javascripts/projects_list.js.coffee
index db5faf71faf..f2887af190b 100644
--- a/app/assets/javascripts/projects_list.js.coffee
+++ b/app/assets/javascripts/projects_list.js.coffee
@@ -8,17 +8,17 @@ class @ProjectsList
$(".projects-list-filter").keyup ->
terms = $(this).val()
- uiBox = $(this).closest('.projects-list-holder')
+ uiBox = $('div.projects-list-holder')
if terms == "" || terms == undefined
- uiBox.find(".projects-list li").show()
+ uiBox.find("ul.projects-list li").show()
else
- uiBox.find(".projects-list li").each (index) ->
- name = $(this).find(".filter-title").text()
+ uiBox.find("ul.projects-list li").each (index) ->
+ name = $(this).find("span.filter-title").text()
if name.toLowerCase().search(terms.toLowerCase()) == -1
$(this).hide()
else
$(this).show()
- uiBox.find(".projects-list li.bottom").hide()
+ uiBox.find("ul.projects-list li.bottom").hide()
diff --git a/app/assets/javascripts/shortcuts.js.coffee b/app/assets/javascripts/shortcuts.js.coffee
index e9aeb1e9525..4d915bfc8c5 100644
--- a/app/assets/javascripts/shortcuts.js.coffee
+++ b/app/assets/javascripts/shortcuts.js.coffee
@@ -7,7 +7,7 @@ class @Shortcuts
selectiveHelp: (e) =>
Shortcuts.showHelp(e, @enabledHelp)
-
+
@showHelp: (e, location) ->
if $('#modal-shortcuts').length > 0
$('#modal-shortcuts').modal('show')
@@ -17,8 +17,7 @@ class @Shortcuts
dataType: 'script',
success: (e) ->
if location and location.length > 0
- for l in location
- $(l).show()
+ $(l).show() for l in location
else
$('.hidden-shortcut').show()
$('.js-more-help-button').remove()
@@ -28,3 +27,8 @@ class @Shortcuts
@focusSearch: (e) ->
$('#search').focus()
e.preventDefault()
+
+$(document).on 'click.more_help', '.js-more-help-button', (e) ->
+ $(@).remove()
+ $('.hidden-shortcut').show()
+ e.preventDefault()
diff --git a/app/assets/javascripts/star.js.coffee b/app/assets/javascripts/star.js.coffee
new file mode 100644
index 00000000000..d849b2e7950
--- /dev/null
+++ b/app/assets/javascripts/star.js.coffee
@@ -0,0 +1,22 @@
+class @Star
+ constructor: ->
+ $('.project-home-panel .toggle-star').on('ajax:success', (e, data, status, xhr) ->
+ $this = $(this)
+ $starSpan = $this.find('span')
+ $starIcon = $this.find('i')
+
+ toggleStar = (isStarred) ->
+ $this.parent().find('span.count').text data.star_count
+ if isStarred
+ $starSpan.removeClass('starred').text 'Star'
+ $starIcon.removeClass('fa-star').addClass 'fa-star-o'
+ else
+ $starSpan.addClass('starred').text 'Unstar'
+ $starIcon.removeClass('fa-star-o').addClass 'fa-star'
+ return
+
+ toggleStar $starSpan.hasClass('starred')
+ return
+ ).on 'ajax:error', (e, xhr, status, error) ->
+ new Flash('Star toggle failed. Try again later.', 'alert')
+ return \ No newline at end of file
diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee
index 12abf806bfa..9467011799f 100644
--- a/app/assets/javascripts/users_select.js.coffee
+++ b/app/assets/javascripts/users_select.js.coffee
@@ -117,5 +117,5 @@ class @UsersSelect
callback(users)
buildUrl: (url) ->
- url = gon.relative_url_root + url if gon.relative_url_root?
+ url = gon.relative_url_root.replace(/\/$/, '') + url if gon.relative_url_root?
return url
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index 7b060ce4853..0c0451fe4dd 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -2,8 +2,8 @@
* This is a manifest file that'll automatically include all the stylesheets available in this directory
* and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
* the top of the compiled file, but it's generally better to create a new file per style scope.
- *= require jquery.ui.datepicker
- *= require jquery.ui.autocomplete
+ *= require jquery-ui/datepicker
+ *= require jquery-ui/autocomplete
*= require jquery.atwho
*= require select2
*= require_self
@@ -48,4 +48,4 @@
/*
* Styles for JS behaviors.
*/
-@import "behaviors.scss"; \ No newline at end of file
+@import "behaviors.scss";
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index a62c0f62a4c..206d39cc9b3 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -76,7 +76,7 @@
.cover-block {
text-align: center;
- background: #f7f8fa;
+ background: $background-color;
margin: -$gl-padding;
margin-bottom: 0;
padding: 44px $gl-padding;
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index fe56266284b..97a94638847 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -1,10 +1,9 @@
@mixin btn-default {
- @include border-radius(2px);
+ @include border-radius(3px);
border-width: 1px;
border-style: solid;
- text-transform: uppercase;
- font-size: 13px;
- font-weight: 600;
+ font-size: 15px;
+ font-weight: 500;
line-height: 18px;
padding: 11px $gl-padding;
letter-spacing: .4px;
@@ -18,7 +17,7 @@
@mixin btn-middle {
@include btn-default;
- @include border-radius(2px);
+ @include border-radius(3px);
padding: 11px 24px;
}
@@ -51,6 +50,10 @@
@include btn-color($blue-light, $border-blue-light, $blue-normal, $border-blue-normal, $blue-dark, $border-blue-dark, #FFFFFF);
}
+@mixin btn-blue-medium {
+ @include btn-color($blue-medium-light, $border-blue-light, $blue-medium, $border-blue-normal, $blue-medium-dark, $border-blue-dark, #FFFFFF);
+}
+
@mixin btn-orange {
@include btn-color($orange-light, $border-orange-light, $orange-normal, $border-orange-normal, $orange-dark, $border-orange-dark, #FFFFFF);
}
@@ -60,7 +63,7 @@
}
@mixin btn-gray {
- @include btn-color($gray-light, $border-gray-light, $gray-normal, $border-gray-normal, $gray-dark, $border-gray-dark, #313236);
+ @include btn-color($gray-light, $border-gray-light, $gray-normal, $border-gray-light, $gray-dark, $border-gray-dark, #313236);
}
@mixin btn-white {
@@ -75,6 +78,10 @@
padding: 5px 10px;
}
+ &.btn-nr {
+ padding: 7px 10px;
+ }
+
&.btn-xs {
padding: 1px 5px;
}
@@ -91,11 +98,15 @@
@include btn-gray;
}
- &.btn-primary,
+ &.btn-primary {
+ @include btn-blue-medium;
+ }
+
&.btn-info {
@include btn-blue;
}
+ &.btn-close,
&.btn-warning {
@include btn-orange;
}
@@ -110,20 +121,8 @@
float: right;
}
- &.btn-close {
- color: $gl-danger;
- border-color: $gl-danger;
- &:hover {
- color: #B94A48;
- }
- }
-
&.btn-reopen {
- color: $gl-success;
- border-color: $gl-success;
- &:hover {
- color: #468847;
- }
+ /* should be same as parent class for now */
}
&.btn-grouped {
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 88da799ee2b..11730000f85 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -374,7 +374,7 @@ table {
}
}
-.center-top-menu {
+.center-top-menu, .left-top-menu {
@include nav-menu;
text-align: center;
margin-top: 5px;
@@ -401,6 +401,16 @@ table {
border-bottom: 1px solid $border-color;
height: 57px;
}
+
+ &.wide {
+ margin-left: -$gl-padding;
+ margin-right: -$gl-padding;
+ }
+}
+
+.left-top-menu {
+ text-align: left;
+ border-bottom: 1px solid #EEE;
}
.center-middle-menu {
diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss
index f12d68b5a1f..e93dbab0c42 100644
--- a/app/assets/stylesheets/framework/issue_box.scss
+++ b/app/assets/stylesheets/framework/issue_box.scss
@@ -4,8 +4,8 @@
*
*/
-.issue-box {
- @include border-radius(2px);
+.status-box {
+ @include border-radius(3px);
display: block;
float: left;
@@ -14,22 +14,22 @@
margin-right: 10px;
font-size: $gl-font-size;
- &.issue-box-closed {
+ &.status-box-closed {
background-color: $gl-danger;
color: #FFF;
}
- &.issue-box-merged {
+ &.status-box-merged {
background-color: $gl-primary;
color: #FFF;
}
- &.issue-box-open {
- background-color: #019875;
+ &.status-box-open {
+ background-color: $green-light;
color: #FFF;
}
- &.issue-box-expired {
+ &.status-box-expired {
background: #cea61b;
color: #FFF;
}
diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss
index aa5acb93cc5..a1a9990241d 100644
--- a/app/assets/stylesheets/framework/layout.scss
+++ b/app/assets/stylesheets/framework/layout.scss
@@ -5,7 +5,7 @@ html {
}
body {
- background-color: #EAEBEC !important;
+ background-color: #F3F3F3 !important;
&.navless {
background-color: white !important;
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index cc48f8c8166..1c74e525a60 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -143,7 +143,11 @@ ul.controls {
> li {
float: left;
- padding-right: 10px;
+ margin-right: 10px;
+
+ &:last-child {
+ margin-right: 0;
+ }
.author_link {
display: inline-block;
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index 2b044786738..4a00a197d9a 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -87,7 +87,7 @@
.new_note,
.edit_note,
-.issuable-description,
+.detail-page-description,
.milestone-description,
.wiki-content,
.merge-request-form {
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index 11c48d26ab5..41fd890f14f 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -123,7 +123,6 @@
padding: 0;
margin: 0;
list-style: none;
- margin-top: 5px;
height: 56px;
li {
@@ -131,9 +130,9 @@
a {
padding: 14px;
- font-size: 17px;
+ font-size: 15px;
line-height: 28px;
- color: #7f8fa4;
+ color: #959494;
border-bottom: 2px solid transparent;
&:hover, &:active, &:focus {
@@ -143,8 +142,8 @@
}
&.active a {
- color: #4c4e54;
- border-bottom: 2px solid #1cacfc;
+ color: #616060;
+ border-bottom: 2px solid #4688f1;
}
.badge {
diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss
index 6f44c323732..c00709fb6bb 100644
--- a/app/assets/stylesheets/framework/mobile.scss
+++ b/app/assets/stylesheets/framework/mobile.scss
@@ -81,7 +81,7 @@
display: none;
}
- .center-top-menu {
+ .center-top-menu, .left-top-menu {
li a {
font-size: 14px;
padding: 19px 10px;
diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss
index eb53c4153d3..ff41e26ed8a 100644
--- a/app/assets/stylesheets/framework/timeline.scss
+++ b/app/assets/stylesheets/framework/timeline.scss
@@ -10,8 +10,7 @@
margin-left: -$gl-padding;
margin-right: -$gl-padding;
color: $gl-gray;
- border-bottom: 1px solid #ECEEF1;
- border-right: 1px solid #ECEEF1;
+ border-bottom: 1px solid $border-white-light;
&:target {
background: $hover;
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 2ef40a6e517..af75123b0af 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -1,9 +1,9 @@
-$hover: #FFFAF1;
+$hover: #faf9f9;
$gl-text-color: #54565B;
$gl-text-green: #4A2;
$gl-text-red: #D12F19;
$gl-text-orange: #D90;
-$gl-header-color: #4c4e54;
+$gl-header-color: #323232;
$gl-link-color: #333c48;
$md-text-color: #444;
$md-link-color: #3084bb;
@@ -15,13 +15,14 @@ $sidebar_width: 230px;
$avatar_radius: 50%;
$code_font_size: 13px;
$code_line_height: 1.5;
-$border-color: #dce0e6;
+$border-color: #efeff1;
$table-border-color: #eef0f2;
-$background-color: #F7F8FA;
+$background-color: #faf9f9;
$header-height: 58px;
$fixed-layout-width: 1280px;
-$gl-gray: #7f8fa4;
+$gl-gray: #5a5a5a;
$gl-padding: 16px;
+$gl-padding-top:10px;
$gl-avatar-size: 46px;
/*
@@ -29,12 +30,12 @@ $gl-avatar-size: 46px;
*/
$white-light: #FFFFFF;
-$white-normal: #DCE0E5;
-$white-dark: #E4E7ED;
+$white-normal: #ededed;
+$white-dark: #ededed;
-$gray-light: #F0F2F5;
-$gray-normal: #DCE0E5;
-$gray-dark: #E4E7ED;
+$gray-light: #f7f7f7;
+$gray-normal: #ededed;
+$gray-dark: #ededed;
$green-light: #31AF64;
$green-normal: #2FAA60;
@@ -44,6 +45,10 @@ $blue-light: #2EA8E5;
$blue-normal: #2D9FD8;
$blue-dark: #2897CE;
+$blue-medium-light: #3498CB;
+$blue-medium: #2F8EBF;
+$blue-medium-dark: #2D86B4;
+
$orange-light: #FC6443;
$orange-normal: #E75E40;
$orange-dark: #CE5237;
@@ -52,11 +57,11 @@ $red-light: #F43263;
$red-normal: #E52C5A;
$red-dark: #D22852;
-$border-white-light: #E3E7EC;
+$border-white-light: #F1F2F4;
$border-white-normal: #D6DAE2;
$border-white-dark: #C6CACF;
-$border-gray-light: #DCE0E5;
+$border-gray-light: #d1d1d1;
$border-gray-normal: #D6DAE2;
$border-gray-dark: #C6CACF;
@@ -76,6 +81,8 @@ $border-red-light: #E52C5A;
$border-red-normal: #D22852;
$border-red-dark: #CA264F;
+/* header */
+$light-grey-header: #faf9f9;
/*
* State colors:
diff --git a/app/assets/stylesheets/pages/awards.scss b/app/assets/stylesheets/pages/awards.scss
index 041b811a606..87dd30f4111 100644
--- a/app/assets/stylesheets/pages/awards.scss
+++ b/app/assets/stylesheets/pages/awards.scss
@@ -2,6 +2,12 @@
@include clearfix;
line-height: 34px;
+ .emoji-icon {
+ width: 20px;
+ height: 20px;
+ margin: 7px 0 0 5px;
+ }
+
.award {
@include border-radius(5px);
@@ -40,6 +46,7 @@
}
.awards-controls {
+ position: relative;
margin-left: 10px;
float: left;
@@ -55,32 +62,64 @@
}
}
- .awards-menu {
- padding: $gl-padding;
- min-width: 214px;
+ .emoji-menu{
+ position: absolute;
+ top: 100%;
+ left: 0;
+ z-index: 1000;
+ display: none;
+ float: left;
+ min-width: 160px;
+ padding: 5px 0;
+ margin: 2px 0 0;
+ font-size: 14px;
+ text-align: left;
+ list-style: none;
+ background-color: #fff;
+ -webkit-background-clip: padding-box;
+ background-clip: padding-box;
+ border: 1px solid #ccc;
+ border: 1px solid rgba(0,0,0,.15);
+ border-radius: 4px;
+ -webkit-box-shadow: 0 6px 12px rgba(0,0,0,.175);
+ box-shadow: 0 6px 12px rgba(0,0,0,.175);
+
+ .emoji-menu-content {
+ padding: $gl-padding;
+ width: 300px;
+ height: 300px;
+ overflow-y: scroll;
+
+ h5 {
+ clear: left;
+ }
- > li {
- cursor: pointer;
- width: 30px;
- height: 30px;
- text-align: center;
- @include border-radius(5px);
+ ul {
+ list-style-type: none;
+ margin-left: -20px;
+ margin-bottom: 20px;
+ overflow: auto;
+ }
- img {
- margin-bottom: 2px;
+ input.emoji-search{
+ background: image-url("icon-search.png") 240px no-repeat;
}
- &:hover {
- background-color: #ccc;
+ li {
+ cursor: pointer;
+ width: 30px;
+ height: 30px;
+ text-align: center;
+ float: left;
+ margin: 3px;
+ list-decorate: none;
+ @include border-radius(5px);
+
+ &:hover {
+ background-color: #ccc;
+ }
}
}
}
}
-
- .awards-menu{
- li {
- float: left;
- margin: 3px;
- }
- }
}
diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss
new file mode 100644
index 00000000000..deab805dbc2
--- /dev/null
+++ b/app/assets/stylesheets/pages/detail_page.scss
@@ -0,0 +1,33 @@
+.detail-page-header {
+ margin: -$gl-padding;
+ padding: 7px $gl-padding;
+ margin-bottom: 0px;
+ border-bottom: 1px solid $border-color;
+ color: #5c5d5e;
+ font-size: 16px;
+ line-height: 34px;
+
+ .author {
+ color: #5c5d5e;
+ }
+
+ .identifier {
+ color: #5c5d5e;
+ }
+}
+
+.detail-page-description {
+ .title {
+ margin: 0;
+ font-size: 23px;
+ color: #313236;
+ }
+
+ .description {
+ margin-top: 6px;
+
+ p:last-child {
+ margin-bottom: 0;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/emojis.scss b/app/assets/stylesheets/pages/emojis.scss
new file mode 100644
index 00000000000..89a94c5a780
--- /dev/null
+++ b/app/assets/stylesheets/pages/emojis.scss
@@ -0,0 +1,1272 @@
+/*
+File is generated by https://github.com/jakesgordon/sprite-factory and midified manualy
+The source: gemojione gem.
+*/
+
+.emoji-icon{
+ background-image: image-url("emoji.png");
+ background-repeat: no-repeat;
+}
+
+.emoji-0023-20E3 { background-position: 0px 0px; }
+.emoji-0030-20E3 { background-position: -20px 0px; }
+.emoji-0031-20E3 { background-position: -40px 0px; }
+.emoji-0032-20E3 { background-position: -60px 0px; }
+.emoji-0033-20E3 { background-position: -80px 0px; }
+.emoji-0034-20E3 { background-position: -100px 0px; }
+.emoji-0035-20E3 { background-position: -120px 0px; }
+.emoji-0036-20E3 { background-position: -140px 0px; }
+.emoji-0037-20E3 { background-position: -160px 0px; }
+.emoji-0038-20E3 { background-position: -180px 0px; }
+.emoji-0039-20E3 { background-position: -200px 0px; }
+.emoji-00A9 { background-position: -220px 0px; }
+.emoji-00AE { background-position: -240px 0px; }
+.emoji-1F004 { background-position: -260px 0px; }
+.emoji-1F0CF { background-position: -280px 0px; }
+.emoji-1F170 { background-position: -300px 0px; }
+.emoji-1F171 { background-position: -320px 0px; }
+.emoji-1F17E { background-position: -340px 0px; }
+.emoji-1F17F { background-position: -360px 0px; }
+.emoji-1F18E { background-position: -380px 0px; }
+.emoji-1F191 { background-position: -400px 0px; }
+.emoji-1F192 { background-position: -420px 0px; }
+.emoji-1F193 { background-position: -440px 0px; }
+.emoji-1F194 { background-position: -460px 0px; }
+.emoji-1F195 { background-position: -480px 0px; }
+.emoji-1F196 { background-position: -500px 0px; }
+.emoji-1F197 { background-position: -520px 0px; }
+.emoji-1F198 { background-position: -540px 0px; }
+.emoji-1F199 { background-position: -560px 0px; }
+.emoji-1F19A { background-position: -580px 0px; }
+.emoji-1F1E6-1F1E8 { background-position: -600px 0px; }
+.emoji-1F1E6-1F1E9 { background-position: -620px 0px; }
+.emoji-1F1E6-1F1EA { background-position: -640px 0px; }
+.emoji-1F1E6-1F1EB { background-position: -660px 0px; }
+.emoji-1F1E6-1F1EC { background-position: -680px 0px; }
+.emoji-1F1E6-1F1EE { background-position: -700px 0px; }
+.emoji-1F1E6-1F1F1 { background-position: -720px 0px; }
+.emoji-1F1E6-1F1F2 { background-position: -740px 0px; }
+.emoji-1F1E6-1F1F4 { background-position: -760px 0px; }
+.emoji-1F1E6-1F1F7 { background-position: -780px 0px; }
+.emoji-1F1E6-1F1F9 { background-position: -800px 0px; }
+.emoji-1F1E6-1F1FA { background-position: -820px 0px; }
+.emoji-1F1E6-1F1FC { background-position: -840px 0px; }
+.emoji-1F1E6-1F1FF { background-position: -860px 0px; }
+.emoji-1F1E7-1F1E6 { background-position: -880px 0px; }
+.emoji-1F1E7-1F1E7 { background-position: -900px 0px; }
+.emoji-1F1E7-1F1E9 { background-position: -920px 0px; }
+.emoji-1F1E7-1F1EA { background-position: -940px 0px; }
+.emoji-1F1E7-1F1EB { background-position: -960px 0px; }
+.emoji-1F1E7-1F1EC { background-position: -980px 0px; }
+.emoji-1F1E7-1F1ED { background-position: -1000px 0px; }
+.emoji-1F1E7-1F1EE { background-position: -1020px 0px; }
+.emoji-1F1E7-1F1EF { background-position: -1040px 0px; }
+.emoji-1F1E7-1F1F2 { background-position: -1060px 0px; }
+.emoji-1F1E7-1F1F3 { background-position: -1080px 0px; }
+.emoji-1F1E7-1F1F4 { background-position: -1100px 0px; }
+.emoji-1F1E7-1F1F7 { background-position: -1120px 0px; }
+.emoji-1F1E7-1F1F8 { background-position: -1140px 0px; }
+.emoji-1F1E7-1F1F9 { background-position: -1160px 0px; }
+.emoji-1F1E7-1F1FC { background-position: -1180px 0px; }
+.emoji-1F1E7-1F1FE { background-position: -1200px 0px; }
+.emoji-1F1E7-1F1FF { background-position: -1220px 0px; }
+.emoji-1F1E8-1F1E6 { background-position: -1240px 0px; }
+.emoji-1F1E8-1F1E9 { background-position: -1260px 0px; }
+.emoji-1F1E8-1F1EB { background-position: -1280px 0px; }
+.emoji-1F1E8-1F1EC { background-position: -1300px 0px; }
+.emoji-1F1E8-1F1ED { background-position: -1320px 0px; }
+.emoji-1F1E8-1F1EE { background-position: -1340px 0px; }
+.emoji-1F1E8-1F1F1 { background-position: -1360px 0px; }
+.emoji-1F1E8-1F1F2 { background-position: -1380px 0px; }
+.emoji-1F1E8-1F1F3 { background-position: -1400px 0px; }
+.emoji-1F1E8-1F1F4 { background-position: -1420px 0px; }
+.emoji-1F1E8-1F1F7 { background-position: -1440px 0px; }
+.emoji-1F1E8-1F1FA { background-position: -1460px 0px; }
+.emoji-1F1E8-1F1FB { background-position: -1480px 0px; }
+.emoji-1F1E8-1F1FE { background-position: -1500px 0px; }
+.emoji-1F1E8-1F1FF { background-position: -1520px 0px; }
+.emoji-1F1E9-1F1EA { background-position: -1540px 0px; }
+.emoji-1F1E9-1F1EF { background-position: -1560px 0px; }
+.emoji-1F1E9-1F1F0 { background-position: -1580px 0px; }
+.emoji-1F1E9-1F1F2 { background-position: -1600px 0px; }
+.emoji-1F1E9-1F1F4 { background-position: -1620px 0px; }
+.emoji-1F1E9-1F1FF { background-position: -1640px 0px; }
+.emoji-1F1EA-1F1E8 { background-position: -1660px 0px; }
+.emoji-1F1EA-1F1EA { background-position: -1680px 0px; }
+.emoji-1F1EA-1F1EC { background-position: -1700px 0px; }
+.emoji-1F1EA-1F1ED { background-position: -1720px 0px; }
+.emoji-1F1EA-1F1F7 { background-position: -1740px 0px; }
+.emoji-1F1EA-1F1F8 { background-position: -1760px 0px; }
+.emoji-1F1EA-1F1F9 { background-position: -1780px 0px; }
+.emoji-1F1EB-1F1EE { background-position: -1800px 0px; }
+.emoji-1F1EB-1F1EF { background-position: -1820px 0px; }
+.emoji-1F1EB-1F1F0 { background-position: -1840px 0px; }
+.emoji-1F1EB-1F1F2 { background-position: -1860px 0px; }
+.emoji-1F1EB-1F1F4 { background-position: -1880px 0px; }
+.emoji-1F1EB-1F1F7 { background-position: -1900px 0px; }
+.emoji-1F1EC-1F1E6 { background-position: -1920px 0px; }
+.emoji-1F1EC-1F1E7 { background-position: -1940px 0px; }
+.emoji-1F1EC-1F1E9 { background-position: -1960px 0px; }
+.emoji-1F1EC-1F1EA { background-position: -1980px 0px; }
+.emoji-1F1EC-1F1ED { background-position: -2000px 0px; }
+.emoji-1F1EC-1F1EE { background-position: -2020px 0px; }
+.emoji-1F1EC-1F1F1 { background-position: -2040px 0px; }
+.emoji-1F1EC-1F1F2 { background-position: -2060px 0px; }
+.emoji-1F1EC-1F1F3 { background-position: -2080px 0px; }
+.emoji-1F1EC-1F1F6 { background-position: -2100px 0px; }
+.emoji-1F1EC-1F1F7 { background-position: -2120px 0px; }
+.emoji-1F1EC-1F1F9 { background-position: -2140px 0px; }
+.emoji-1F1EC-1F1FA { background-position: -2160px 0px; }
+.emoji-1F1EC-1F1FC { background-position: -2180px 0px; }
+.emoji-1F1EC-1F1FE { background-position: -2200px 0px; }
+.emoji-1F1ED-1F1F0 { background-position: -2220px 0px; }
+.emoji-1F1ED-1F1F3 { background-position: -2240px 0px; }
+.emoji-1F1ED-1F1F7 { background-position: -2260px 0px; }
+.emoji-1F1ED-1F1F9 { background-position: -2280px 0px; }
+.emoji-1F1ED-1F1FA { background-position: -2300px 0px; }
+.emoji-1F1EE-1F1E9 { background-position: -2320px 0px; }
+.emoji-1F1EE-1F1EA { background-position: -2340px 0px; }
+.emoji-1F1EE-1F1F1 { background-position: -2360px 0px; }
+.emoji-1F1EE-1F1F3 { background-position: -2380px 0px; }
+.emoji-1F1EE-1F1F6 { background-position: -2400px 0px; }
+.emoji-1F1EE-1F1F7 { background-position: -2420px 0px; }
+.emoji-1F1EE-1F1F8 { background-position: -2440px 0px; }
+.emoji-1F1EE-1F1F9 { background-position: -2460px 0px; }
+.emoji-1F1EF-1F1EA { background-position: -2480px 0px; }
+.emoji-1F1EF-1F1F2 { background-position: -2500px 0px; }
+.emoji-1F1EF-1F1F4 { background-position: -2520px 0px; }
+.emoji-1F1EF-1F1F5 { background-position: -2540px 0px; }
+.emoji-1F1F0-1F1EA { background-position: -2560px 0px; }
+.emoji-1F1F0-1F1EC { background-position: -2580px 0px; }
+.emoji-1F1F0-1F1ED { background-position: -2600px 0px; }
+.emoji-1F1F0-1F1EE { background-position: -2620px 0px; }
+.emoji-1F1F0-1F1F2 { background-position: -2640px 0px; }
+.emoji-1F1F0-1F1F3 { background-position: -2660px 0px; }
+.emoji-1F1F0-1F1F5 { background-position: -2680px 0px; }
+.emoji-1F1F0-1F1F7 { background-position: -2700px 0px; }
+.emoji-1F1F0-1F1FC { background-position: -2720px 0px; }
+.emoji-1F1F0-1F1FE { background-position: -2740px 0px; }
+.emoji-1F1F0-1F1FF { background-position: -2760px 0px; }
+.emoji-1F1F1-1F1E6 { background-position: -2780px 0px; }
+.emoji-1F1F1-1F1E7 { background-position: -2800px 0px; }
+.emoji-1F1F1-1F1E8 { background-position: -2820px 0px; }
+.emoji-1F1F1-1F1EE { background-position: -2840px 0px; }
+.emoji-1F1F1-1F1F0 { background-position: -2860px 0px; }
+.emoji-1F1F1-1F1F7 { background-position: -2880px 0px; }
+.emoji-1F1F1-1F1F8 { background-position: -2900px 0px; }
+.emoji-1F1F1-1F1F9 { background-position: -2920px 0px; }
+.emoji-1F1F1-1F1FA { background-position: -2940px 0px; }
+.emoji-1F1F1-1F1FB { background-position: -2960px 0px; }
+.emoji-1F1F1-1F1FE { background-position: -2980px 0px; }
+.emoji-1F1F2-1F1E6 { background-position: -3000px 0px; }
+.emoji-1F1F2-1F1E8 { background-position: -3020px 0px; }
+.emoji-1F1F2-1F1E9 { background-position: -3040px 0px; }
+.emoji-1F1F2-1F1EA { background-position: -3060px 0px; }
+.emoji-1F1F2-1F1EC { background-position: -3080px 0px; }
+.emoji-1F1F2-1F1ED { background-position: -3100px 0px; }
+.emoji-1F1F2-1F1F0 { background-position: -3120px 0px; }
+.emoji-1F1F2-1F1F1 { background-position: -3140px 0px; }
+.emoji-1F1F2-1F1F2 { background-position: -3160px 0px; }
+.emoji-1F1F2-1F1F3 { background-position: -3180px 0px; }
+.emoji-1F1F2-1F1F4 { background-position: -3200px 0px; }
+.emoji-1F1F2-1F1F7 { background-position: -3220px 0px; }
+.emoji-1F1F2-1F1F8 { background-position: -3240px 0px; }
+.emoji-1F1F2-1F1F9 { background-position: -3260px 0px; }
+.emoji-1F1F2-1F1FA { background-position: -3280px 0px; }
+.emoji-1F1F2-1F1FB { background-position: -3300px 0px; }
+.emoji-1F1F2-1F1FC { background-position: -3320px 0px; }
+.emoji-1F1F2-1F1FD { background-position: -3340px 0px; }
+.emoji-1F1F2-1F1FE { background-position: -3360px 0px; }
+.emoji-1F1F2-1F1FF { background-position: -3380px 0px; }
+.emoji-1F1F3-1F1E6 { background-position: -3400px 0px; }
+.emoji-1F1F3-1F1E8 { background-position: -3420px 0px; }
+.emoji-1F1F3-1F1EA { background-position: -3440px 0px; }
+.emoji-1F1F3-1F1EC { background-position: -3460px 0px; }
+.emoji-1F1F3-1F1EE { background-position: -3480px 0px; }
+.emoji-1F1F3-1F1F1 { background-position: -3500px 0px; }
+.emoji-1F1F3-1F1F4 { background-position: -3520px 0px; }
+.emoji-1F1F3-1F1F5 { background-position: -3540px 0px; }
+.emoji-1F1F3-1F1F7 { background-position: -3560px 0px; }
+.emoji-1F1F3-1F1FA { background-position: -3580px 0px; }
+.emoji-1F1F3-1F1FF { background-position: -3600px 0px; }
+.emoji-1F1F4-1F1F2 { background-position: -3620px 0px; }
+.emoji-1F1F5-1F1E6 { background-position: -3640px 0px; }
+.emoji-1F1F5-1F1EA { background-position: -3660px 0px; }
+.emoji-1F1F5-1F1EB { background-position: -3680px 0px; }
+.emoji-1F1F5-1F1EC { background-position: -3700px 0px; }
+.emoji-1F1F5-1F1ED { background-position: -3720px 0px; }
+.emoji-1F1F5-1F1F0 { background-position: -3740px 0px; }
+.emoji-1F1F5-1F1F1 { background-position: -3760px 0px; }
+.emoji-1F1F5-1F1F7 { background-position: -3780px 0px; }
+.emoji-1F1F5-1F1F8 { background-position: -3800px 0px; }
+.emoji-1F1F5-1F1F9 { background-position: -3820px 0px; }
+.emoji-1F1F5-1F1FC { background-position: -3840px 0px; }
+.emoji-1F1F5-1F1FE { background-position: -3860px 0px; }
+.emoji-1F1F6-1F1E6 { background-position: -3880px 0px; }
+.emoji-1F1F7-1F1F4 { background-position: -3900px 0px; }
+.emoji-1F1F7-1F1F8 { background-position: -3920px 0px; }
+.emoji-1F1F7-1F1FA { background-position: -3940px 0px; }
+.emoji-1F1F7-1F1FC { background-position: -3960px 0px; }
+.emoji-1F1F8-1F1E6 { background-position: -3980px 0px; }
+.emoji-1F1F8-1F1E7 { background-position: -4000px 0px; }
+.emoji-1F1F8-1F1E8 { background-position: -4020px 0px; }
+.emoji-1F1F8-1F1E9 { background-position: -4040px 0px; }
+.emoji-1F1F8-1F1EA { background-position: -4060px 0px; }
+.emoji-1F1F8-1F1EC { background-position: -4080px 0px; }
+.emoji-1F1F8-1F1ED { background-position: -4100px 0px; }
+.emoji-1F1F8-1F1EE { background-position: -4120px 0px; }
+.emoji-1F1F8-1F1F0 { background-position: -4140px 0px; }
+.emoji-1F1F8-1F1F1 { background-position: -4160px 0px; }
+.emoji-1F1F8-1F1F2 { background-position: -4180px 0px; }
+.emoji-1F1F8-1F1F3 { background-position: -4200px 0px; }
+.emoji-1F1F8-1F1F4 { background-position: -4220px 0px; }
+.emoji-1F1F8-1F1F7 { background-position: -4240px 0px; }
+.emoji-1F1F8-1F1F9 { background-position: -4260px 0px; }
+.emoji-1F1F8-1F1FB { background-position: -4280px 0px; }
+.emoji-1F1F8-1F1FE { background-position: -4300px 0px; }
+.emoji-1F1F8-1F1FF { background-position: -4320px 0px; }
+.emoji-1F1F9-1F1E9 { background-position: -4340px 0px; }
+.emoji-1F1F9-1F1EC { background-position: -4360px 0px; }
+.emoji-1F1F9-1F1ED { background-position: -4380px 0px; }
+.emoji-1F1F9-1F1EF { background-position: -4400px 0px; }
+.emoji-1F1F9-1F1F1 { background-position: -4420px 0px; }
+.emoji-1F1F9-1F1F2 { background-position: -4440px 0px; }
+.emoji-1F1F9-1F1F3 { background-position: -4460px 0px; }
+.emoji-1F1F9-1F1F4 { background-position: -4480px 0px; }
+.emoji-1F1F9-1F1F7 { background-position: -4500px 0px; }
+.emoji-1F1F9-1F1F9 { background-position: -4520px 0px; }
+.emoji-1F1F9-1F1FB { background-position: -4540px 0px; }
+.emoji-1F1F9-1F1FC { background-position: -4560px 0px; }
+.emoji-1F1F9-1F1FF { background-position: -4580px 0px; }
+.emoji-1F1FA-1F1E6 { background-position: -4600px 0px; }
+.emoji-1F1FA-1F1EC { background-position: -4620px 0px; }
+.emoji-1F1FA-1F1F8 { background-position: -4640px 0px; }
+.emoji-1F1FA-1F1FE { background-position: -4660px 0px; }
+.emoji-1F1FA-1F1FF { background-position: -4680px 0px; }
+.emoji-1F1FB-1F1E6 { background-position: -4700px 0px; }
+.emoji-1F1FB-1F1E8 { background-position: -4720px 0px; }
+.emoji-1F1FB-1F1EA { background-position: -4740px 0px; }
+.emoji-1F1FB-1F1EE { background-position: -4760px 0px; }
+.emoji-1F1FB-1F1F3 { background-position: -4780px 0px; }
+.emoji-1F1FB-1F1FA { background-position: -4800px 0px; }
+.emoji-1F1FC-1F1EB { background-position: -4820px 0px; }
+.emoji-1F1FC-1F1F8 { background-position: -4840px 0px; }
+.emoji-1F1FD-1F1F0 { background-position: -4860px 0px; }
+.emoji-1F1FE-1F1EA { background-position: -4880px 0px; }
+.emoji-1F1FF-1F1E6 { background-position: -4900px 0px; }
+.emoji-1F1FF-1F1F2 { background-position: -4920px 0px; }
+.emoji-1F1FF-1F1FC { background-position: -4940px 0px; }
+.emoji-1F201 { background-position: -4960px 0px; }
+.emoji-1F202 { background-position: -4980px 0px; }
+.emoji-1F21A { background-position: -5000px 0px; }
+.emoji-1F22F { background-position: -5020px 0px; }
+.emoji-1F232 { background-position: -5040px 0px; }
+.emoji-1F233 { background-position: -5060px 0px; }
+.emoji-1F234 { background-position: -5080px 0px; }
+.emoji-1F235 { background-position: -5100px 0px; }
+.emoji-1F236 { background-position: -5120px 0px; }
+.emoji-1F237 { background-position: -5140px 0px; }
+.emoji-1F238 { background-position: -5160px 0px; }
+.emoji-1F239 { background-position: -5180px 0px; }
+.emoji-1F23A { background-position: -5200px 0px; }
+.emoji-1F250 { background-position: -5220px 0px; }
+.emoji-1F251 { background-position: -5240px 0px; }
+.emoji-1F300 { background-position: -5260px 0px; }
+.emoji-1F301 { background-position: -5280px 0px; }
+.emoji-1F302 { background-position: -5300px 0px; }
+.emoji-1F303 { background-position: -5320px 0px; }
+.emoji-1F304 { background-position: -5340px 0px; }
+.emoji-1F305 { background-position: -5360px 0px; }
+.emoji-1F306 { background-position: -5380px 0px; }
+.emoji-1F307 { background-position: -5400px 0px; }
+.emoji-1F308 { background-position: -5420px 0px; }
+.emoji-1F309 { background-position: -5440px 0px; }
+.emoji-1F30A { background-position: -5460px 0px; }
+.emoji-1F30B { background-position: -5480px 0px; }
+.emoji-1F30C { background-position: -5500px 0px; }
+.emoji-1F30D { background-position: -5520px 0px; }
+.emoji-1F30E { background-position: -5540px 0px; }
+.emoji-1F30F { background-position: -5560px 0px; }
+.emoji-1F310 { background-position: -5580px 0px; }
+.emoji-1F311 { background-position: -5600px 0px; }
+.emoji-1F312 { background-position: -5620px 0px; }
+.emoji-1F313 { background-position: -5640px 0px; }
+.emoji-1F314 { background-position: -5660px 0px; }
+.emoji-1F315 { background-position: -5680px 0px; }
+.emoji-1F316 { background-position: -5700px 0px; }
+.emoji-1F317 { background-position: -5720px 0px; }
+.emoji-1F318 { background-position: -5740px 0px; }
+.emoji-1F319 { background-position: -5760px 0px; }
+.emoji-1F31A { background-position: -5780px 0px; }
+.emoji-1F31B { background-position: -5800px 0px; }
+.emoji-1F31C { background-position: -5820px 0px; }
+.emoji-1F31D { background-position: -5840px 0px; }
+.emoji-1F31E { background-position: -5860px 0px; }
+.emoji-1F31F { background-position: -5880px 0px; }
+.emoji-1F320 { background-position: -5900px 0px; }
+.emoji-1F321 { background-position: -5920px 0px; }
+.emoji-1F327 { background-position: -5940px 0px; }
+.emoji-1F328 { background-position: -5960px 0px; }
+.emoji-1F329 { background-position: -5980px 0px; }
+.emoji-1F32A { background-position: -6000px 0px; }
+.emoji-1F32B { background-position: -6020px 0px; }
+.emoji-1F32C { background-position: -6040px 0px; }
+.emoji-1F330 { background-position: -6060px 0px; }
+.emoji-1F331 { background-position: -6080px 0px; }
+.emoji-1F332 { background-position: -6100px 0px; }
+.emoji-1F333 { background-position: -6120px 0px; }
+.emoji-1F334 { background-position: -6140px 0px; }
+.emoji-1F335 { background-position: -6160px 0px; }
+.emoji-1F336 { background-position: -6180px 0px; }
+.emoji-1F337 { background-position: -6200px 0px; }
+.emoji-1F338 { background-position: -6220px 0px; }
+.emoji-1F339 { background-position: -6240px 0px; }
+.emoji-1F33A { background-position: -6260px 0px; }
+.emoji-1F33B { background-position: -6280px 0px; }
+.emoji-1F33C { background-position: -6300px 0px; }
+.emoji-1F33D { background-position: -6320px 0px; }
+.emoji-1F33E { background-position: -6340px 0px; }
+.emoji-1F33F { background-position: -6360px 0px; }
+.emoji-1F340 { background-position: -6380px 0px; }
+.emoji-1F341 { background-position: -6400px 0px; }
+.emoji-1F342 { background-position: -6420px 0px; }
+.emoji-1F343 { background-position: -6440px 0px; }
+.emoji-1F344 { background-position: -6460px 0px; }
+.emoji-1F345 { background-position: -6480px 0px; }
+.emoji-1F346 { background-position: -6500px 0px; }
+.emoji-1F347 { background-position: -6520px 0px; }
+.emoji-1F348 { background-position: -6540px 0px; }
+.emoji-1F349 { background-position: -6560px 0px; }
+.emoji-1F34A { background-position: -6580px 0px; }
+.emoji-1F34B { background-position: -6600px 0px; }
+.emoji-1F34C { background-position: -6620px 0px; }
+.emoji-1F34D { background-position: -6640px 0px; }
+.emoji-1F34E { background-position: -6660px 0px; }
+.emoji-1F34F { background-position: -6680px 0px; }
+.emoji-1F350 { background-position: -6700px 0px; }
+.emoji-1F351 { background-position: -6720px 0px; }
+.emoji-1F352 { background-position: -6740px 0px; }
+.emoji-1F353 { background-position: -6760px 0px; }
+.emoji-1F354 { background-position: -6780px 0px; }
+.emoji-1F355 { background-position: -6800px 0px; }
+.emoji-1F356 { background-position: -6820px 0px; }
+.emoji-1F357 { background-position: -6840px 0px; }
+.emoji-1F358 { background-position: -6860px 0px; }
+.emoji-1F359 { background-position: -6880px 0px; }
+.emoji-1F35A { background-position: -6900px 0px; }
+.emoji-1F35B { background-position: -6920px 0px; }
+.emoji-1F35C { background-position: -6940px 0px; }
+.emoji-1F35D { background-position: -6960px 0px; }
+.emoji-1F35E { background-position: -6980px 0px; }
+.emoji-1F35F { background-position: -7000px 0px; }
+.emoji-1F360 { background-position: -7020px 0px; }
+.emoji-1F361 { background-position: -7040px 0px; }
+.emoji-1F362 { background-position: -7060px 0px; }
+.emoji-1F363 { background-position: -7080px 0px; }
+.emoji-1F364 { background-position: -7100px 0px; }
+.emoji-1F365 { background-position: -7120px 0px; }
+.emoji-1F366 { background-position: -7140px 0px; }
+.emoji-1F367 { background-position: -7160px 0px; }
+.emoji-1F368 { background-position: -7180px 0px; }
+.emoji-1F369 { background-position: -7200px 0px; }
+.emoji-1F36A { background-position: -7220px 0px; }
+.emoji-1F36B { background-position: -7240px 0px; }
+.emoji-1F36C { background-position: -7260px 0px; }
+.emoji-1F36D { background-position: -7280px 0px; }
+.emoji-1F36E { background-position: -7300px 0px; }
+.emoji-1F36F { background-position: -7320px 0px; }
+.emoji-1F370 { background-position: -7340px 0px; }
+.emoji-1F371 { background-position: -7360px 0px; }
+.emoji-1F372 { background-position: -7380px 0px; }
+.emoji-1F373 { background-position: -7400px 0px; }
+.emoji-1F374 { background-position: -7420px 0px; }
+.emoji-1F375 { background-position: -7440px 0px; }
+.emoji-1F376 { background-position: -7460px 0px; }
+.emoji-1F377 { background-position: -7480px 0px; }
+.emoji-1F378 { background-position: -7500px 0px; }
+.emoji-1F379 { background-position: -7520px 0px; }
+.emoji-1F37A { background-position: -7540px 0px; }
+.emoji-1F37B { background-position: -7560px 0px; }
+.emoji-1F37C { background-position: -7580px 0px; }
+.emoji-1F37D { background-position: -7600px 0px; }
+.emoji-1F380 { background-position: -7620px 0px; }
+.emoji-1F381 { background-position: -7640px 0px; }
+.emoji-1F382 { background-position: -7660px 0px; }
+.emoji-1F383 { background-position: -7680px 0px; }
+.emoji-1F384 { background-position: -7700px 0px; }
+.emoji-1F385 { background-position: -7720px 0px; }
+.emoji-1F386 { background-position: -7740px 0px; }
+.emoji-1F387 { background-position: -7760px 0px; }
+.emoji-1F388 { background-position: -7780px 0px; }
+.emoji-1F389 { background-position: -7800px 0px; }
+.emoji-1F38A { background-position: -7820px 0px; }
+.emoji-1F38B { background-position: -7840px 0px; }
+.emoji-1F38C { background-position: -7860px 0px; }
+.emoji-1F38D { background-position: -7880px 0px; }
+.emoji-1F38E { background-position: -7900px 0px; }
+.emoji-1F38F { background-position: -7920px 0px; }
+.emoji-1F390 { background-position: -7940px 0px; }
+.emoji-1F391 { background-position: -7960px 0px; }
+.emoji-1F392 { background-position: -7980px 0px; }
+.emoji-1F393 { background-position: -8000px 0px; }
+.emoji-1F394 { background-position: -8020px 0px; }
+.emoji-1F395 { background-position: -8040px 0px; }
+.emoji-1F396 { background-position: -8060px 0px; }
+.emoji-1F397 { background-position: -8080px 0px; }
+.emoji-1F398 { background-position: -8100px 0px; }
+.emoji-1F399 { background-position: -8120px 0px; }
+.emoji-1F39A { background-position: -8140px 0px; }
+.emoji-1F39B { background-position: -8160px 0px; }
+.emoji-1F39C { background-position: -8180px 0px; }
+.emoji-1F39D { background-position: -8200px 0px; }
+.emoji-1F39E { background-position: -8220px 0px; }
+.emoji-1F39F { background-position: -8240px 0px; }
+.emoji-1F3A0 { background-position: -8260px 0px; }
+.emoji-1F3A1 { background-position: -8280px 0px; }
+.emoji-1F3A2 { background-position: -8300px 0px; }
+.emoji-1F3A3 { background-position: -8320px 0px; }
+.emoji-1F3A4 { background-position: -8340px 0px; }
+.emoji-1F3A5 { background-position: -8360px 0px; }
+.emoji-1F3A6 { background-position: -8380px 0px; }
+.emoji-1F3A7 { background-position: -8400px 0px; }
+.emoji-1F3A8 { background-position: -8420px 0px; }
+.emoji-1F3A9 { background-position: -8440px 0px; }
+.emoji-1F3AA { background-position: -8460px 0px; }
+.emoji-1F3AB { background-position: -8480px 0px; }
+.emoji-1F3AC { background-position: -8500px 0px; }
+.emoji-1F3AD { background-position: -8520px 0px; }
+.emoji-1F3AE { background-position: -8540px 0px; }
+.emoji-1F3AF { background-position: -8560px 0px; }
+.emoji-1F3B0 { background-position: -8580px 0px; }
+.emoji-1F3B1 { background-position: -8600px 0px; }
+.emoji-1F3B2 { background-position: -8620px 0px; }
+.emoji-1F3B3 { background-position: -8640px 0px; }
+.emoji-1F3B4 { background-position: -8660px 0px; }
+.emoji-1F3B5 { background-position: -8680px 0px; }
+.emoji-1F3B6 { background-position: -8700px 0px; }
+.emoji-1F3B7 { background-position: -8720px 0px; }
+.emoji-1F3B8 { background-position: -8740px 0px; }
+.emoji-1F3B9 { background-position: -8760px 0px; }
+.emoji-1F3BA { background-position: -8780px 0px; }
+.emoji-1F3BB { background-position: -8800px 0px; }
+.emoji-1F3BC { background-position: -8820px 0px; }
+.emoji-1F3BD { background-position: -8840px 0px; }
+.emoji-1F3BE { background-position: -8860px 0px; }
+.emoji-1F3BF { background-position: -8880px 0px; }
+.emoji-1F3C0 { background-position: -8900px 0px; }
+.emoji-1F3C1 { background-position: -8920px 0px; }
+.emoji-1F3C2 { background-position: -8940px 0px; }
+.emoji-1F3C3 { background-position: -8960px 0px; }
+.emoji-1F3C4 { background-position: -8980px 0px; }
+.emoji-1F3C5 { background-position: -9000px 0px; }
+.emoji-1F3C6 { background-position: -9020px 0px; }
+.emoji-1F3C7 { background-position: -9040px 0px; }
+.emoji-1F3C8 { background-position: -9060px 0px; }
+.emoji-1F3C9 { background-position: -9080px 0px; }
+.emoji-1F3CA { background-position: -9100px 0px; }
+.emoji-1F3CB { background-position: -9120px 0px; }
+.emoji-1F3CC { background-position: -9140px 0px; }
+.emoji-1F3CD { background-position: -9160px 0px; }
+.emoji-1F3CE { background-position: -9180px 0px; }
+.emoji-1F3D4 { background-position: -9200px 0px; }
+.emoji-1F3D5 { background-position: -9220px 0px; }
+.emoji-1F3D6 { background-position: -9240px 0px; }
+.emoji-1F3D7 { background-position: -9260px 0px; }
+.emoji-1F3D8 { background-position: -9280px 0px; }
+.emoji-1F3D9 { background-position: -9300px 0px; }
+.emoji-1F3DA { background-position: -9320px 0px; }
+.emoji-1F3DB { background-position: -9340px 0px; }
+.emoji-1F3DC { background-position: -9360px 0px; }
+.emoji-1F3DD { background-position: -9380px 0px; }
+.emoji-1F3DE { background-position: -9400px 0px; }
+.emoji-1F3DF { background-position: -9420px 0px; }
+.emoji-1F3E0 { background-position: -9440px 0px; }
+.emoji-1F3E1 { background-position: -9460px 0px; }
+.emoji-1F3E2 { background-position: -9480px 0px; }
+.emoji-1F3E3 { background-position: -9500px 0px; }
+.emoji-1F3E4 { background-position: -9520px 0px; }
+.emoji-1F3E5 { background-position: -9540px 0px; }
+.emoji-1F3E6 { background-position: -9560px 0px; }
+.emoji-1F3E7 { background-position: -9580px 0px; }
+.emoji-1F3E8 { background-position: -9600px 0px; }
+.emoji-1F3E9 { background-position: -9620px 0px; }
+.emoji-1F3EA { background-position: -9640px 0px; }
+.emoji-1F3EB { background-position: -9660px 0px; }
+.emoji-1F3EC { background-position: -9680px 0px; }
+.emoji-1F3ED { background-position: -9700px 0px; }
+.emoji-1F3EE { background-position: -9720px 0px; }
+.emoji-1F3EF { background-position: -9740px 0px; }
+.emoji-1F3F0 { background-position: -9760px 0px; }
+.emoji-1F3F1 { background-position: -9780px 0px; }
+.emoji-1F3F2 { background-position: -9800px 0px; }
+.emoji-1F3F3 { background-position: -9820px 0px; }
+.emoji-1F3F4 { background-position: -9840px 0px; }
+.emoji-1F3F5 { background-position: -9860px 0px; }
+.emoji-1F3F6 { background-position: -9880px 0px; }
+.emoji-1F3F7 { background-position: -9900px 0px; }
+.emoji-1F400 { background-position: -9920px 0px; }
+.emoji-1F401 { background-position: -9940px 0px; }
+.emoji-1F402 { background-position: -9960px 0px; }
+.emoji-1F403 { background-position: -9980px 0px; }
+.emoji-1F404 { background-position: -10000px 0px; }
+.emoji-1F405 { background-position: -10020px 0px; }
+.emoji-1F406 { background-position: -10040px 0px; }
+.emoji-1F407 { background-position: -10060px 0px; }
+.emoji-1F408 { background-position: -10080px 0px; }
+.emoji-1F409 { background-position: -10100px 0px; }
+.emoji-1F40A { background-position: -10120px 0px; }
+.emoji-1F40B { background-position: -10140px 0px; }
+.emoji-1F40C { background-position: -10160px 0px; }
+.emoji-1F40D { background-position: -10180px 0px; }
+.emoji-1F40E { background-position: -10200px 0px; }
+.emoji-1F40F { background-position: -10220px 0px; }
+.emoji-1F410 { background-position: -10240px 0px; }
+.emoji-1F411 { background-position: -10260px 0px; }
+.emoji-1F412 { background-position: -10280px 0px; }
+.emoji-1F413 { background-position: -10300px 0px; }
+.emoji-1F414 { background-position: -10320px 0px; }
+.emoji-1F415 { background-position: -10340px 0px; }
+.emoji-1F416 { background-position: -10360px 0px; }
+.emoji-1F417 { background-position: -10380px 0px; }
+.emoji-1F418 { background-position: -10400px 0px; }
+.emoji-1F419 { background-position: -10420px 0px; }
+.emoji-1F41A { background-position: -10440px 0px; }
+.emoji-1F41B { background-position: -10460px 0px; }
+.emoji-1F41C { background-position: -10480px 0px; }
+.emoji-1F41D { background-position: -10500px 0px; }
+.emoji-1F41E { background-position: -10520px 0px; }
+.emoji-1F41F { background-position: -10540px 0px; }
+.emoji-1F420 { background-position: -10560px 0px; }
+.emoji-1F421 { background-position: -10580px 0px; }
+.emoji-1F422 { background-position: -10600px 0px; }
+.emoji-1F423 { background-position: -10620px 0px; }
+.emoji-1F424 { background-position: -10640px 0px; }
+.emoji-1F425 { background-position: -10660px 0px; }
+.emoji-1F426 { background-position: -10680px 0px; }
+.emoji-1F427 { background-position: -10700px 0px; }
+.emoji-1F428 { background-position: -10720px 0px; }
+.emoji-1F429 { background-position: -10740px 0px; }
+.emoji-1F42A { background-position: -10760px 0px; }
+.emoji-1F42B { background-position: -10780px 0px; }
+.emoji-1F42C { background-position: -10800px 0px; }
+.emoji-1F42D { background-position: -10820px 0px; }
+.emoji-1F42E { background-position: -10840px 0px; }
+.emoji-1F42F { background-position: -10860px 0px; }
+.emoji-1F430 { background-position: -10880px 0px; }
+.emoji-1F431 { background-position: -10900px 0px; }
+.emoji-1F432 { background-position: -10920px 0px; }
+.emoji-1F433 { background-position: -10940px 0px; }
+.emoji-1F434 { background-position: -10960px 0px; }
+.emoji-1F435 { background-position: -10980px 0px; }
+.emoji-1F436 { background-position: -11000px 0px; }
+.emoji-1F437 { background-position: -11020px 0px; }
+.emoji-1F438 { background-position: -11040px 0px; }
+.emoji-1F439 { background-position: -11060px 0px; }
+.emoji-1F43A { background-position: -11080px 0px; }
+.emoji-1F43B { background-position: -11100px 0px; }
+.emoji-1F43C { background-position: -11120px 0px; }
+.emoji-1F43D { background-position: -11140px 0px; }
+.emoji-1F43E { background-position: -11160px 0px; }
+.emoji-1F43F { background-position: -11180px 0px; }
+.emoji-1F440 { background-position: -11200px 0px; }
+.emoji-1F441 { background-position: -11220px 0px; }
+.emoji-1F442 { background-position: -11240px 0px; }
+.emoji-1F443 { background-position: -11260px 0px; }
+.emoji-1F444 { background-position: -11280px 0px; }
+.emoji-1F445 { background-position: -11300px 0px; }
+.emoji-1F446 { background-position: -11320px 0px; }
+.emoji-1F447 { background-position: -11340px 0px; }
+.emoji-1F448 { background-position: -11360px 0px; }
+.emoji-1F449 { background-position: -11380px 0px; }
+.emoji-1F44A { background-position: -11400px 0px; }
+.emoji-1F44B { background-position: -11420px 0px; }
+.emoji-1F44C { background-position: -11440px 0px; }
+.emoji-1F44D { background-position: -11460px 0px; }
+.emoji-1F44E { background-position: -11480px 0px; }
+.emoji-1F44F { background-position: -11500px 0px; }
+.emoji-1F450 { background-position: -11520px 0px; }
+.emoji-1F451 { background-position: -11540px 0px; }
+.emoji-1F452 { background-position: -11560px 0px; }
+.emoji-1F453 { background-position: -11580px 0px; }
+.emoji-1F454 { background-position: -11600px 0px; }
+.emoji-1F455 { background-position: -11620px 0px; }
+.emoji-1F456 { background-position: -11640px 0px; }
+.emoji-1F457 { background-position: -11660px 0px; }
+.emoji-1F458 { background-position: -11680px 0px; }
+.emoji-1F459 { background-position: -11700px 0px; }
+.emoji-1F45A { background-position: -11720px 0px; }
+.emoji-1F45B { background-position: -11740px 0px; }
+.emoji-1F45C { background-position: -11760px 0px; }
+.emoji-1F45D { background-position: -11780px 0px; }
+.emoji-1F45E { background-position: -11800px 0px; }
+.emoji-1F45F { background-position: -11820px 0px; }
+.emoji-1F460 { background-position: -11840px 0px; }
+.emoji-1F461 { background-position: -11860px 0px; }
+.emoji-1F462 { background-position: -11880px 0px; }
+.emoji-1F463 { background-position: -11900px 0px; }
+.emoji-1F464 { background-position: -11920px 0px; }
+.emoji-1F465 { background-position: -11940px 0px; }
+.emoji-1F466 { background-position: -11960px 0px; }
+.emoji-1F467 { background-position: -11980px 0px; }
+.emoji-1F468 { background-position: -12000px 0px; }
+.emoji-1F468-1F468-1F466 { background-position: -12020px 0px; }
+.emoji-1F468-1F468-1F466-1F466 { background-position: -12040px 0px; }
+.emoji-1F468-1F468-1F467 { background-position: -12060px 0px; }
+.emoji-1F468-1F468-1F467-1F466 { background-position: -12080px 0px; }
+.emoji-1F468-1F468-1F467-1F467 { background-position: -12100px 0px; }
+.emoji-1F468-1F469-1F466-1F466 { background-position: -12120px 0px; }
+.emoji-1F468-1F469-1F467 { background-position: -12140px 0px; }
+.emoji-1F468-1F469-1F467-1F466 { background-position: -12160px 0px; }
+.emoji-1F468-1F469-1F467-1F467 { background-position: -12180px 0px; }
+.emoji-1F468-2764-1F468 { background-position: -12200px 0px; }
+.emoji-1F468-2764-1F48B-1F468 { background-position: -12220px 0px; }
+.emoji-1F469 { background-position: -12240px 0px; }
+.emoji-1F469-1F469-1F466 { background-position: -12260px 0px; }
+.emoji-1F469-1F469-1F466-1F466 { background-position: -12280px 0px; }
+.emoji-1F469-1F469-1F467 { background-position: -12300px 0px; }
+.emoji-1F469-1F469-1F467-1F466 { background-position: -12320px 0px; }
+.emoji-1F469-1F469-1F467-1F467 { background-position: -12340px 0px; }
+.emoji-1F469-2764-1F469 { background-position: -12360px 0px; }
+.emoji-1F469-2764-1F48B-1F469 { background-position: -12380px 0px; }
+.emoji-1F46A { background-position: -12400px 0px; }
+.emoji-1F46B { background-position: -12420px 0px; }
+.emoji-1F46C { background-position: -12440px 0px; }
+.emoji-1F46D { background-position: -12460px 0px; }
+.emoji-1F46E { background-position: -12480px 0px; }
+.emoji-1F46F { background-position: -12500px 0px; }
+.emoji-1F470 { background-position: -12520px 0px; }
+.emoji-1F471 { background-position: -12540px 0px; }
+.emoji-1F472 { background-position: -12560px 0px; }
+.emoji-1F473 { background-position: -12580px 0px; }
+.emoji-1F474 { background-position: -12600px 0px; }
+.emoji-1F475 { background-position: -12620px 0px; }
+.emoji-1F476 { background-position: -12640px 0px; }
+.emoji-1F477 { background-position: -12660px 0px; }
+.emoji-1F478 { background-position: -12680px 0px; }
+.emoji-1F479 { background-position: -12700px 0px; }
+.emoji-1F47A { background-position: -12720px 0px; }
+.emoji-1F47B { background-position: -12740px 0px; }
+.emoji-1F47C { background-position: -12760px 0px; }
+.emoji-1F47D { background-position: -12780px 0px; }
+.emoji-1F47E { background-position: -12800px 0px; }
+.emoji-1F47F { background-position: -12820px 0px; }
+.emoji-1F480 { background-position: -12840px 0px; }
+.emoji-1F481 { background-position: -12860px 0px; }
+.emoji-1F482 { background-position: -12880px 0px; }
+.emoji-1F483 { background-position: -12900px 0px; }
+.emoji-1F484 { background-position: -12920px 0px; }
+.emoji-1F485 { background-position: -12940px 0px; }
+.emoji-1F486 { background-position: -12960px 0px; }
+.emoji-1F487 { background-position: -12980px 0px; }
+.emoji-1F488 { background-position: -13000px 0px; }
+.emoji-1F489 { background-position: -13020px 0px; }
+.emoji-1F48A { background-position: -13040px 0px; }
+.emoji-1F48B { background-position: -13060px 0px; }
+.emoji-1F48C { background-position: -13080px 0px; }
+.emoji-1F48D { background-position: -13100px 0px; }
+.emoji-1F48E { background-position: -13120px 0px; }
+.emoji-1F48F { background-position: -13140px 0px; }
+.emoji-1F490 { background-position: -13160px 0px; }
+.emoji-1F491 { background-position: -13180px 0px; }
+.emoji-1F492 { background-position: -13200px 0px; }
+.emoji-1F493 { background-position: -13220px 0px; }
+.emoji-1F494 { background-position: -13240px 0px; }
+.emoji-1F495 { background-position: -13260px 0px; }
+.emoji-1F496 { background-position: -13280px 0px; }
+.emoji-1F497 { background-position: -13300px 0px; }
+.emoji-1F498 { background-position: -13320px 0px; }
+.emoji-1F499 { background-position: -13340px 0px; }
+.emoji-1F49A { background-position: -13360px 0px; }
+.emoji-1F49B { background-position: -13380px 0px; }
+.emoji-1F49C { background-position: -13400px 0px; }
+.emoji-1F49D { background-position: -13420px 0px; }
+.emoji-1F49E { background-position: -13440px 0px; }
+.emoji-1F49F { background-position: -13460px 0px; }
+.emoji-1F4A0 { background-position: -13480px 0px; }
+.emoji-1F4A1 { background-position: -13500px 0px; }
+.emoji-1F4A2 { background-position: -13520px 0px; }
+.emoji-1F4A3 { background-position: -13540px 0px; }
+.emoji-1F4A4 { background-position: -13560px 0px; }
+.emoji-1F4A5 { background-position: -13580px 0px; }
+.emoji-1F4A6 { background-position: -13600px 0px; }
+.emoji-1F4A7 { background-position: -13620px 0px; }
+.emoji-1F4A8 { background-position: -13640px 0px; }
+.emoji-1F4A9 { background-position: -13660px 0px; }
+.emoji-1F4AA { background-position: -13680px 0px; }
+.emoji-1F4AB { background-position: -13700px 0px; }
+.emoji-1F4AC { background-position: -13720px 0px; }
+.emoji-1F4AD { background-position: -13740px 0px; }
+.emoji-1F4AE { background-position: -13760px 0px; }
+.emoji-1F4AF { background-position: -13780px 0px; }
+.emoji-1F4B0 { background-position: -13800px 0px; }
+.emoji-1F4B1 { background-position: -13820px 0px; }
+.emoji-1F4B2 { background-position: -13840px 0px; }
+.emoji-1F4B3 { background-position: -13860px 0px; }
+.emoji-1F4B4 { background-position: -13880px 0px; }
+.emoji-1F4B5 { background-position: -13900px 0px; }
+.emoji-1F4B6 { background-position: -13920px 0px; }
+.emoji-1F4B7 { background-position: -13940px 0px; }
+.emoji-1F4B8 { background-position: -13960px 0px; }
+.emoji-1F4B9 { background-position: -13980px 0px; }
+.emoji-1F4BA { background-position: -14000px 0px; }
+.emoji-1F4BB { background-position: -14020px 0px; }
+.emoji-1F4BC { background-position: -14040px 0px; }
+.emoji-1F4BD { background-position: -14060px 0px; }
+.emoji-1F4BE { background-position: -14080px 0px; }
+.emoji-1F4BF { background-position: -14100px 0px; }
+.emoji-1F4C0 { background-position: -14120px 0px; }
+.emoji-1F4C1 { background-position: -14140px 0px; }
+.emoji-1F4C2 { background-position: -14160px 0px; }
+.emoji-1F4C3 { background-position: -14180px 0px; }
+.emoji-1F4C4 { background-position: -14200px 0px; }
+.emoji-1F4C5 { background-position: -14220px 0px; }
+.emoji-1F4C6 { background-position: -14240px 0px; }
+.emoji-1F4C7 { background-position: -14260px 0px; }
+.emoji-1F4C8 { background-position: -14280px 0px; }
+.emoji-1F4C9 { background-position: -14300px 0px; }
+.emoji-1F4CA { background-position: -14320px 0px; }
+.emoji-1F4CB { background-position: -14340px 0px; }
+.emoji-1F4CC { background-position: -14360px 0px; }
+.emoji-1F4CD { background-position: -14380px 0px; }
+.emoji-1F4CE { background-position: -14400px 0px; }
+.emoji-1F4CF { background-position: -14420px 0px; }
+.emoji-1F4D0 { background-position: -14440px 0px; }
+.emoji-1F4D1 { background-position: -14460px 0px; }
+.emoji-1F4D2 { background-position: -14480px 0px; }
+.emoji-1F4D3 { background-position: -14500px 0px; }
+.emoji-1F4D4 { background-position: -14520px 0px; }
+.emoji-1F4D5 { background-position: -14540px 0px; }
+.emoji-1F4D6 { background-position: -14560px 0px; }
+.emoji-1F4D7 { background-position: -14580px 0px; }
+.emoji-1F4D8 { background-position: -14600px 0px; }
+.emoji-1F4D9 { background-position: -14620px 0px; }
+.emoji-1F4DA { background-position: -14640px 0px; }
+.emoji-1F4DB { background-position: -14660px 0px; }
+.emoji-1F4DC { background-position: -14680px 0px; }
+.emoji-1F4DD { background-position: -14700px 0px; }
+.emoji-1F4DE { background-position: -14720px 0px; }
+.emoji-1F4DF { background-position: -14740px 0px; }
+.emoji-1F4E0 { background-position: -14760px 0px; }
+.emoji-1F4E1 { background-position: -14780px 0px; }
+.emoji-1F4E2 { background-position: -14800px 0px; }
+.emoji-1F4E3 { background-position: -14820px 0px; }
+.emoji-1F4E4 { background-position: -14840px 0px; }
+.emoji-1F4E5 { background-position: -14860px 0px; }
+.emoji-1F4E6 { background-position: -14880px 0px; }
+.emoji-1F4E7 { background-position: -14900px 0px; }
+.emoji-1F4E8 { background-position: -14920px 0px; }
+.emoji-1F4E9 { background-position: -14940px 0px; }
+.emoji-1F4EA { background-position: -14960px 0px; }
+.emoji-1F4EB { background-position: -14980px 0px; }
+.emoji-1F4EC { background-position: -15000px 0px; }
+.emoji-1F4ED { background-position: -15020px 0px; }
+.emoji-1F4EE { background-position: -15040px 0px; }
+.emoji-1F4EF { background-position: -15060px 0px; }
+.emoji-1F4F0 { background-position: -15080px 0px; }
+.emoji-1F4F1 { background-position: -15100px 0px; }
+.emoji-1F4F2 { background-position: -15120px 0px; }
+.emoji-1F4F3 { background-position: -15140px 0px; }
+.emoji-1F4F4 { background-position: -15160px 0px; }
+.emoji-1F4F5 { background-position: -15180px 0px; }
+.emoji-1F4F6 { background-position: -15200px 0px; }
+.emoji-1F4F7 { background-position: -15220px 0px; }
+.emoji-1F4F8 { background-position: -15240px 0px; }
+.emoji-1F4F9 { background-position: -15260px 0px; }
+.emoji-1F4FA { background-position: -15280px 0px; }
+.emoji-1F4FB { background-position: -15300px 0px; }
+.emoji-1F4FC { background-position: -15320px 0px; }
+.emoji-1F4FD { background-position: -15340px 0px; }
+.emoji-1F4FE { background-position: -15360px 0px; }
+.emoji-1F500 { background-position: -15380px 0px; }
+.emoji-1F501 { background-position: -15400px 0px; }
+.emoji-1F502 { background-position: -15420px 0px; }
+.emoji-1F503 { background-position: -15440px 0px; }
+.emoji-1F504 { background-position: -15460px 0px; }
+.emoji-1F505 { background-position: -15480px 0px; }
+.emoji-1F506 { background-position: -15500px 0px; }
+.emoji-1F507 { background-position: -15520px 0px; }
+.emoji-1F508 { background-position: -15540px 0px; }
+.emoji-1F509 { background-position: -15560px 0px; }
+.emoji-1F50A { background-position: -15580px 0px; }
+.emoji-1F50B { background-position: -15600px 0px; }
+.emoji-1F50C { background-position: -15620px 0px; }
+.emoji-1F50D { background-position: -15640px 0px; }
+.emoji-1F50E { background-position: -15660px 0px; }
+.emoji-1F50F { background-position: -15680px 0px; }
+.emoji-1F510 { background-position: -15700px 0px; }
+.emoji-1F511 { background-position: -15720px 0px; }
+.emoji-1F512 { background-position: -15740px 0px; }
+.emoji-1F513 { background-position: -15760px 0px; }
+.emoji-1F514 { background-position: -15780px 0px; }
+.emoji-1F515 { background-position: -15800px 0px; }
+.emoji-1F516 { background-position: -15820px 0px; }
+.emoji-1F517 { background-position: -15840px 0px; }
+.emoji-1F518 { background-position: -15860px 0px; }
+.emoji-1F519 { background-position: -15880px 0px; }
+.emoji-1F51A { background-position: -15900px 0px; }
+.emoji-1F51B { background-position: -15920px 0px; }
+.emoji-1F51C { background-position: -15940px 0px; }
+.emoji-1F51D { background-position: -15960px 0px; }
+.emoji-1F51E { background-position: -15980px 0px; }
+.emoji-1F51F { background-position: -16000px 0px; }
+.emoji-1F520 { background-position: -16020px 0px; }
+.emoji-1F521 { background-position: -16040px 0px; }
+.emoji-1F522 { background-position: -16060px 0px; }
+.emoji-1F523 { background-position: -16080px 0px; }
+.emoji-1F524 { background-position: -16100px 0px; }
+.emoji-1F525 { background-position: -16120px 0px; }
+.emoji-1F526 { background-position: -16140px 0px; }
+.emoji-1F527 { background-position: -16160px 0px; }
+.emoji-1F528 { background-position: -16180px 0px; }
+.emoji-1F529 { background-position: -16200px 0px; }
+.emoji-1F52A { background-position: -16220px 0px; }
+.emoji-1F52B { background-position: -16240px 0px; }
+.emoji-1F52C { background-position: -16260px 0px; }
+.emoji-1F52D { background-position: -16280px 0px; }
+.emoji-1F52E { background-position: -16300px 0px; }
+.emoji-1F52F { background-position: -16320px 0px; }
+.emoji-1F530 { background-position: -16340px 0px; }
+.emoji-1F531 { background-position: -16360px 0px; }
+.emoji-1F532 { background-position: -16380px 0px; }
+.emoji-1F533 { background-position: -16400px 0px; }
+.emoji-1F534 { background-position: -16420px 0px; }
+.emoji-1F535 { background-position: -16440px 0px; }
+.emoji-1F536 { background-position: -16460px 0px; }
+.emoji-1F537 { background-position: -16480px 0px; }
+.emoji-1F538 { background-position: -16500px 0px; }
+.emoji-1F539 { background-position: -16520px 0px; }
+.emoji-1F53A { background-position: -16540px 0px; }
+.emoji-1F53B { background-position: -16560px 0px; }
+.emoji-1F53C { background-position: -16580px 0px; }
+.emoji-1F53D { background-position: -16600px 0px; }
+.emoji-1F546 { background-position: -16620px 0px; }
+.emoji-1F547 { background-position: -16640px 0px; }
+.emoji-1F548 { background-position: -16660px 0px; }
+.emoji-1F549 { background-position: -16680px 0px; }
+.emoji-1F54A { background-position: -16700px 0px; }
+.emoji-1F550 { background-position: -16720px 0px; }
+.emoji-1F551 { background-position: -16740px 0px; }
+.emoji-1F552 { background-position: -16760px 0px; }
+.emoji-1F553 { background-position: -16780px 0px; }
+.emoji-1F554 { background-position: -16800px 0px; }
+.emoji-1F555 { background-position: -16820px 0px; }
+.emoji-1F556 { background-position: -16840px 0px; }
+.emoji-1F557 { background-position: -16860px 0px; }
+.emoji-1F558 { background-position: -16880px 0px; }
+.emoji-1F559 { background-position: -16900px 0px; }
+.emoji-1F55A { background-position: -16920px 0px; }
+.emoji-1F55B { background-position: -16940px 0px; }
+.emoji-1F55C { background-position: -16960px 0px; }
+.emoji-1F55D { background-position: -16980px 0px; }
+.emoji-1F55E { background-position: -17000px 0px; }
+.emoji-1F55F { background-position: -17020px 0px; }
+.emoji-1F560 { background-position: -17040px 0px; }
+.emoji-1F561 { background-position: -17060px 0px; }
+.emoji-1F562 { background-position: -17080px 0px; }
+.emoji-1F563 { background-position: -17100px 0px; }
+.emoji-1F564 { background-position: -17120px 0px; }
+.emoji-1F565 { background-position: -17140px 0px; }
+.emoji-1F566 { background-position: -17160px 0px; }
+.emoji-1F567 { background-position: -17180px 0px; }
+.emoji-1F568 { background-position: -17200px 0px; }
+.emoji-1F569 { background-position: -17220px 0px; }
+.emoji-1F56A { background-position: -17240px 0px; }
+.emoji-1F56B { background-position: -17260px 0px; }
+.emoji-1F56C { background-position: -17280px 0px; }
+.emoji-1F56D { background-position: -17300px 0px; }
+.emoji-1F56E { background-position: -17320px 0px; }
+.emoji-1F56F { background-position: -17340px 0px; }
+.emoji-1F570 { background-position: -17360px 0px; }
+.emoji-1F571 { background-position: -17380px 0px; }
+.emoji-1F572 { background-position: -17400px 0px; }
+.emoji-1F573 { background-position: -17420px 0px; }
+.emoji-1F574 { background-position: -17440px 0px; }
+.emoji-1F575 { background-position: -17460px 0px; }
+.emoji-1F576 { background-position: -17480px 0px; }
+.emoji-1F577 { background-position: -17500px 0px; }
+.emoji-1F578 { background-position: -17520px 0px; }
+.emoji-1F579 { background-position: -17540px 0px; }
+.emoji-1F57B { background-position: -17560px 0px; }
+.emoji-1F57E { background-position: -17580px 0px; }
+.emoji-1F57F { background-position: -17600px 0px; }
+.emoji-1F581 { background-position: -17620px 0px; }
+.emoji-1F582 { background-position: -17640px 0px; }
+.emoji-1F583 { background-position: -17660px 0px; }
+.emoji-1F585 { background-position: -17680px 0px; }
+.emoji-1F586 { background-position: -17700px 0px; }
+.emoji-1F587 { background-position: -17720px 0px; }
+.emoji-1F588 { background-position: -17740px 0px; }
+.emoji-1F589 { background-position: -17760px 0px; }
+.emoji-1F58A { background-position: -17780px 0px; }
+.emoji-1F58B { background-position: -17800px 0px; }
+.emoji-1F58C { background-position: -17820px 0px; }
+.emoji-1F58D { background-position: -17840px 0px; }
+.emoji-1F58E { background-position: -17860px 0px; }
+.emoji-1F58F { background-position: -17880px 0px; }
+.emoji-1F590 { background-position: -17900px 0px; }
+.emoji-1F591 { background-position: -17920px 0px; }
+.emoji-1F592 { background-position: -17940px 0px; }
+.emoji-1F593 { background-position: -17960px 0px; }
+.emoji-1F594 { background-position: -17980px 0px; }
+.emoji-1F595 { background-position: -18000px 0px; }
+.emoji-1F596 { background-position: -18020px 0px; }
+.emoji-1F597 { background-position: -18040px 0px; }
+.emoji-1F598 { background-position: -18060px 0px; }
+.emoji-1F599 { background-position: -18080px 0px; }
+.emoji-1F59E { background-position: -18100px 0px; }
+.emoji-1F59F { background-position: -18120px 0px; }
+.emoji-1F5A5 { background-position: -18140px 0px; }
+.emoji-1F5A6 { background-position: -18160px 0px; }
+.emoji-1F5A7 { background-position: -18180px 0px; }
+.emoji-1F5A8 { background-position: -18200px 0px; }
+.emoji-1F5A9 { background-position: -18220px 0px; }
+.emoji-1F5AA { background-position: -18240px 0px; }
+.emoji-1F5AB { background-position: -18260px 0px; }
+.emoji-1F5AD { background-position: -18280px 0px; }
+.emoji-1F5AE { background-position: -18300px 0px; }
+.emoji-1F5AF { background-position: -18320px 0px; }
+.emoji-1F5B2 { background-position: -18340px 0px; }
+.emoji-1F5B3 { background-position: -18360px 0px; }
+.emoji-1F5B4 { background-position: -18380px 0px; }
+.emoji-1F5B8 { background-position: -18400px 0px; }
+.emoji-1F5B9 { background-position: -18420px 0px; }
+.emoji-1F5BC { background-position: -18440px 0px; }
+.emoji-1F5BD { background-position: -18460px 0px; }
+.emoji-1F5BE { background-position: -18480px 0px; }
+.emoji-1F5C0 { background-position: -18500px 0px; }
+.emoji-1F5C1 { background-position: -18520px 0px; }
+.emoji-1F5C2 { background-position: -18540px 0px; }
+.emoji-1F5C3 { background-position: -18560px 0px; }
+.emoji-1F5C4 { background-position: -18580px 0px; }
+.emoji-1F5C6 { background-position: -18600px 0px; }
+.emoji-1F5C7 { background-position: -18620px 0px; }
+.emoji-1F5C9 { background-position: -18640px 0px; }
+.emoji-1F5CA { background-position: -18660px 0px; }
+.emoji-1F5CE { background-position: -18680px 0px; }
+.emoji-1F5CF { background-position: -18700px 0px; }
+.emoji-1F5D0 { background-position: -18720px 0px; }
+.emoji-1F5D1 { background-position: -18740px 0px; }
+.emoji-1F5D2 { background-position: -18760px 0px; }
+.emoji-1F5D3 { background-position: -18780px 0px; }
+.emoji-1F5D4 { background-position: -18800px 0px; }
+.emoji-1F5D8 { background-position: -18820px 0px; }
+.emoji-1F5D9 { background-position: -18840px 0px; }
+.emoji-1F5DC { background-position: -18860px 0px; }
+.emoji-1F5DD { background-position: -18880px 0px; }
+.emoji-1F5DE { background-position: -18900px 0px; }
+.emoji-1F5E0 { background-position: -18920px 0px; }
+.emoji-1F5E1 { background-position: -18940px 0px; }
+.emoji-1F5E2 { background-position: -18960px 0px; }
+.emoji-1F5E3 { background-position: -18980px 0px; }
+.emoji-1F5E8 { background-position: -19000px 0px; }
+.emoji-1F5E9 { background-position: -19020px 0px; }
+.emoji-1F5EA { background-position: -19040px 0px; }
+.emoji-1F5EB { background-position: -19060px 0px; }
+.emoji-1F5EC { background-position: -19080px 0px; }
+.emoji-1F5ED { background-position: -19100px 0px; }
+.emoji-1F5EE { background-position: -19120px 0px; }
+.emoji-1F5EF { background-position: -19140px 0px; }
+.emoji-1F5F0 { background-position: -19160px 0px; }
+.emoji-1F5F1 { background-position: -19180px 0px; }
+.emoji-1F5F2 { background-position: -19200px 0px; }
+.emoji-1F5F3 { background-position: -19220px 0px; }
+.emoji-1F5F4 { background-position: -19240px 0px; }
+.emoji-1F5F5 { background-position: -19260px 0px; }
+.emoji-1F5F8 { background-position: -19280px 0px; }
+.emoji-1F5F9 { background-position: -19300px 0px; }
+.emoji-1F5FA { background-position: -19320px 0px; }
+.emoji-1F5FB { background-position: -19340px 0px; }
+.emoji-1F5FC { background-position: -19360px 0px; }
+.emoji-1F5FD { background-position: -19380px 0px; }
+.emoji-1F5FE { background-position: -19400px 0px; }
+.emoji-1F5FF { background-position: -19420px 0px; }
+.emoji-1F600 { background-position: -19440px 0px; }
+.emoji-1F601 { background-position: -19460px 0px; }
+.emoji-1F602 { background-position: -19480px 0px; }
+.emoji-1F603 { background-position: -19500px 0px; }
+.emoji-1F604 { background-position: -19520px 0px; }
+.emoji-1F605 { background-position: -19540px 0px; }
+.emoji-1F606 { background-position: -19560px 0px; }
+.emoji-1F607 { background-position: -19580px 0px; }
+.emoji-1F608 { background-position: -19600px 0px; }
+.emoji-1F609 { background-position: -19620px 0px; }
+.emoji-1F60A { background-position: -19640px 0px; }
+.emoji-1F60B { background-position: -19660px 0px; }
+.emoji-1F60C { background-position: -19680px 0px; }
+.emoji-1F60D { background-position: -19700px 0px; }
+.emoji-1F60E { background-position: -19720px 0px; }
+.emoji-1F60F { background-position: -19740px 0px; }
+.emoji-1F610 { background-position: -19760px 0px; }
+.emoji-1F611 { background-position: -19780px 0px; }
+.emoji-1F612 { background-position: -19800px 0px; }
+.emoji-1F613 { background-position: -19820px 0px; }
+.emoji-1F614 { background-position: -19840px 0px; }
+.emoji-1F615 { background-position: -19860px 0px; }
+.emoji-1F616 { background-position: -19880px 0px; }
+.emoji-1F617 { background-position: -19900px 0px; }
+.emoji-1F618 { background-position: -19920px 0px; }
+.emoji-1F619 { background-position: -19940px 0px; }
+.emoji-1F61A { background-position: -19960px 0px; }
+.emoji-1F61B { background-position: -19980px 0px; }
+.emoji-1F61C { background-position: -20000px 0px; }
+.emoji-1F61D { background-position: -20020px 0px; }
+.emoji-1F61E { background-position: -20040px 0px; }
+.emoji-1F61F { background-position: -20060px 0px; }
+.emoji-1F620 { background-position: -20080px 0px; }
+.emoji-1F621 { background-position: -20100px 0px; }
+.emoji-1F622 { background-position: -20120px 0px; }
+.emoji-1F623 { background-position: -20140px 0px; }
+.emoji-1F624 { background-position: -20160px 0px; }
+.emoji-1F625 { background-position: -20180px 0px; }
+.emoji-1F626 { background-position: -20200px 0px; }
+.emoji-1F627 { background-position: -20220px 0px; }
+.emoji-1F628 { background-position: -20240px 0px; }
+.emoji-1F629 { background-position: -20260px 0px; }
+.emoji-1F62A { background-position: -20280px 0px; }
+.emoji-1F62B { background-position: -20300px 0px; }
+.emoji-1F62C { background-position: -20320px 0px; }
+.emoji-1F62D { background-position: -20340px 0px; }
+.emoji-1F62E { background-position: -20360px 0px; }
+.emoji-1F62F { background-position: -20380px 0px; }
+.emoji-1F630 { background-position: -20400px 0px; }
+.emoji-1F631 { background-position: -20420px 0px; }
+.emoji-1F632 { background-position: -20440px 0px; }
+.emoji-1F633 { background-position: -20460px 0px; }
+.emoji-1F634 { background-position: -20480px 0px; }
+.emoji-1F635 { background-position: -20500px 0px; }
+.emoji-1F636 { background-position: -20520px 0px; }
+.emoji-1F637 { background-position: -20540px 0px; }
+.emoji-1F638 { background-position: -20560px 0px; }
+.emoji-1F639 { background-position: -20580px 0px; }
+.emoji-1F63A { background-position: -20600px 0px; }
+.emoji-1F63B { background-position: -20620px 0px; }
+.emoji-1F63C { background-position: -20640px 0px; }
+.emoji-1F63D { background-position: -20660px 0px; }
+.emoji-1F63E { background-position: -20680px 0px; }
+.emoji-1F63F { background-position: -20700px 0px; }
+.emoji-1F640 { background-position: -20720px 0px; }
+.emoji-1F641 { background-position: -20740px 0px; }
+.emoji-1F642 { background-position: -20760px 0px; }
+.emoji-1F645 { background-position: -20780px 0px; }
+.emoji-1F646 { background-position: -20800px 0px; }
+.emoji-1F647 { background-position: -20820px 0px; }
+.emoji-1F648 { background-position: -20840px 0px; }
+.emoji-1F649 { background-position: -20860px 0px; }
+.emoji-1F64A { background-position: -20880px 0px; }
+.emoji-1F64B { background-position: -20900px 0px; }
+.emoji-1F64C { background-position: -20920px 0px; }
+.emoji-1F64D { background-position: -20940px 0px; }
+.emoji-1F64E { background-position: -20960px 0px; }
+.emoji-1F64F { background-position: -20980px 0px; }
+.emoji-1F680 { background-position: -21000px 0px; }
+.emoji-1F681 { background-position: -21020px 0px; }
+.emoji-1F682 { background-position: -21040px 0px; }
+.emoji-1F683 { background-position: -21060px 0px; }
+.emoji-1F684 { background-position: -21080px 0px; }
+.emoji-1F685 { background-position: -21100px 0px; }
+.emoji-1F686 { background-position: -21120px 0px; }
+.emoji-1F687 { background-position: -21140px 0px; }
+.emoji-1F688 { background-position: -21160px 0px; }
+.emoji-1F689 { background-position: -21180px 0px; }
+.emoji-1F68A { background-position: -21200px 0px; }
+.emoji-1F68B { background-position: -21220px 0px; }
+.emoji-1F68C { background-position: -21240px 0px; }
+.emoji-1F68D { background-position: -21260px 0px; }
+.emoji-1F68E { background-position: -21280px 0px; }
+.emoji-1F68F { background-position: -21300px 0px; }
+.emoji-1F690 { background-position: -21320px 0px; }
+.emoji-1F691 { background-position: -21340px 0px; }
+.emoji-1F692 { background-position: -21360px 0px; }
+.emoji-1F693 { background-position: -21380px 0px; }
+.emoji-1F694 { background-position: -21400px 0px; }
+.emoji-1F695 { background-position: -21420px 0px; }
+.emoji-1F696 { background-position: -21440px 0px; }
+.emoji-1F697 { background-position: -21460px 0px; }
+.emoji-1F698 { background-position: -21480px 0px; }
+.emoji-1F699 { background-position: -21500px 0px; }
+.emoji-1F69A { background-position: -21520px 0px; }
+.emoji-1F69B { background-position: -21540px 0px; }
+.emoji-1F69C { background-position: -21560px 0px; }
+.emoji-1F69D { background-position: -21580px 0px; }
+.emoji-1F69E { background-position: -21600px 0px; }
+.emoji-1F69F { background-position: -21620px 0px; }
+.emoji-1F6A0 { background-position: -21640px 0px; }
+.emoji-1F6A1 { background-position: -21660px 0px; }
+.emoji-1F6A2 { background-position: -21680px 0px; }
+.emoji-1F6A3 { background-position: -21700px 0px; }
+.emoji-1F6A4 { background-position: -21720px 0px; }
+.emoji-1F6A5 { background-position: -21740px 0px; }
+.emoji-1F6A6 { background-position: -21760px 0px; }
+.emoji-1F6A7 { background-position: -21780px 0px; }
+.emoji-1F6A8 { background-position: -21800px 0px; }
+.emoji-1F6A9 { background-position: -21820px 0px; }
+.emoji-1F6AA { background-position: -21840px 0px; }
+.emoji-1F6AB { background-position: -21860px 0px; }
+.emoji-1F6AC { background-position: -21880px 0px; }
+.emoji-1F6AD { background-position: -21900px 0px; }
+.emoji-1F6AE { background-position: -21920px 0px; }
+.emoji-1F6AF { background-position: -21940px 0px; }
+.emoji-1F6B0 { background-position: -21960px 0px; }
+.emoji-1F6B1 { background-position: -21980px 0px; }
+.emoji-1F6B2 { background-position: -22000px 0px; }
+.emoji-1F6B3 { background-position: -22020px 0px; }
+.emoji-1F6B4 { background-position: -22040px 0px; }
+.emoji-1F6B5 { background-position: -22060px 0px; }
+.emoji-1F6B6 { background-position: -22080px 0px; }
+.emoji-1F6B7 { background-position: -22100px 0px; }
+.emoji-1F6B8 { background-position: -22120px 0px; }
+.emoji-1F6B9 { background-position: -22140px 0px; }
+.emoji-1F6BA { background-position: -22160px 0px; }
+.emoji-1F6BB { background-position: -22180px 0px; }
+.emoji-1F6BC { background-position: -22200px 0px; }
+.emoji-1F6BD { background-position: -22220px 0px; }
+.emoji-1F6BE { background-position: -22240px 0px; }
+.emoji-1F6BF { background-position: -22260px 0px; }
+.emoji-1F6C0 { background-position: -22280px 0px; }
+.emoji-1F6C1 { background-position: -22300px 0px; }
+.emoji-1F6C2 { background-position: -22320px 0px; }
+.emoji-1F6C3 { background-position: -22340px 0px; }
+.emoji-1F6C4 { background-position: -22360px 0px; }
+.emoji-1F6C5 { background-position: -22380px 0px; }
+.emoji-1F6C6 { background-position: -22400px 0px; }
+.emoji-1F6C7 { background-position: -22420px 0px; }
+.emoji-1F6C8 { background-position: -22440px 0px; }
+.emoji-1F6C9 { background-position: -22460px 0px; }
+.emoji-1F6CA { background-position: -22480px 0px; }
+.emoji-1F6CB { background-position: -22500px 0px; }
+.emoji-1F6CC { background-position: -22520px 0px; }
+.emoji-1F6CD { background-position: -22540px 0px; }
+.emoji-1F6CE { background-position: -22560px 0px; }
+.emoji-1F6CF { background-position: -22580px 0px; }
+.emoji-1F6E0 { background-position: -22600px 0px; }
+.emoji-1F6E1 { background-position: -22620px 0px; }
+.emoji-1F6E2 { background-position: -22640px 0px; }
+.emoji-1F6E3 { background-position: -22660px 0px; }
+.emoji-1F6E4 { background-position: -22680px 0px; }
+.emoji-1F6E5 { background-position: -22700px 0px; }
+.emoji-1F6E6 { background-position: -22720px 0px; }
+.emoji-1F6E7 { background-position: -22740px 0px; }
+.emoji-1F6E8 { background-position: -22760px 0px; }
+.emoji-1F6E9 { background-position: -22780px 0px; }
+.emoji-1F6EA { background-position: -22800px 0px; }
+.emoji-1F6EB { background-position: -22820px 0px; }
+.emoji-1F6EC { background-position: -22840px 0px; }
+.emoji-1F6F0 { background-position: -22860px 0px; }
+.emoji-1F6F1 { background-position: -22880px 0px; }
+.emoji-1F6F2 { background-position: -22900px 0px; }
+.emoji-1F6F3 { background-position: -22920px 0px; }
+.emoji-203C { background-position: -22940px 0px; }
+.emoji-2049 { background-position: -22960px 0px; }
+.emoji-2122 { background-position: -22980px 0px; }
+.emoji-2139 { background-position: -23000px 0px; }
+.emoji-2194 { background-position: -23020px 0px; }
+.emoji-2195 { background-position: -23040px 0px; }
+.emoji-2196 { background-position: -23060px 0px; }
+.emoji-2197 { background-position: -23080px 0px; }
+.emoji-2198 { background-position: -23100px 0px; }
+.emoji-2199 { background-position: -23120px 0px; }
+.emoji-21A9 { background-position: -23140px 0px; }
+.emoji-21AA { background-position: -23160px 0px; }
+.emoji-231A { background-position: -23180px 0px; }
+.emoji-231B { background-position: -23200px 0px; }
+.emoji-23E9 { background-position: -23220px 0px; }
+.emoji-23EA { background-position: -23240px 0px; }
+.emoji-23EB { background-position: -23260px 0px; }
+.emoji-23EC { background-position: -23280px 0px; }
+.emoji-23F0 { background-position: -23300px 0px; }
+.emoji-23F3 { background-position: -23320px 0px; }
+.emoji-24C2 { background-position: -23340px 0px; }
+.emoji-25AA { background-position: -23360px 0px; }
+.emoji-25AB { background-position: -23380px 0px; }
+.emoji-25B6 { background-position: -23400px 0px; }
+.emoji-25C0 { background-position: -23420px 0px; }
+.emoji-25FB { background-position: -23440px 0px; }
+.emoji-25FC { background-position: -23460px 0px; }
+.emoji-25FD { background-position: -23480px 0px; }
+.emoji-25FE { background-position: -23500px 0px; }
+.emoji-2600 { background-position: -23520px 0px; }
+.emoji-2601 { background-position: -23540px 0px; }
+.emoji-260E { background-position: -23560px 0px; }
+.emoji-2611 { background-position: -23580px 0px; }
+.emoji-2614 { background-position: -23600px 0px; }
+.emoji-2615 { background-position: -23620px 0px; }
+.emoji-261D { background-position: -23640px 0px; }
+.emoji-263A { background-position: -23660px 0px; }
+.emoji-2648 { background-position: -23680px 0px; }
+.emoji-2649 { background-position: -23700px 0px; }
+.emoji-264A { background-position: -23720px 0px; }
+.emoji-264B { background-position: -23740px 0px; }
+.emoji-264C { background-position: -23760px 0px; }
+.emoji-264D { background-position: -23780px 0px; }
+.emoji-264E { background-position: -23800px 0px; }
+.emoji-264F { background-position: -23820px 0px; }
+.emoji-2650 { background-position: -23840px 0px; }
+.emoji-2651 { background-position: -23860px 0px; }
+.emoji-2652 { background-position: -23880px 0px; }
+.emoji-2653 { background-position: -23900px 0px; }
+.emoji-2660 { background-position: -23920px 0px; }
+.emoji-2663 { background-position: -23940px 0px; }
+.emoji-2665 { background-position: -23960px 0px; }
+.emoji-2666 { background-position: -23980px 0px; }
+.emoji-2668 { background-position: -24000px 0px; }
+.emoji-267B { background-position: -24020px 0px; }
+.emoji-267F { background-position: -24040px 0px; }
+.emoji-2693 { background-position: -24060px 0px; }
+.emoji-26A0 { background-position: -24080px 0px; }
+.emoji-26A1 { background-position: -24100px 0px; }
+.emoji-26AA { background-position: -24120px 0px; }
+.emoji-26AB { background-position: -24140px 0px; }
+.emoji-26BD { background-position: -24160px 0px; }
+.emoji-26BE { background-position: -24180px 0px; }
+.emoji-26C4 { background-position: -24200px 0px; }
+.emoji-26C5 { background-position: -24220px 0px; }
+.emoji-26CE { background-position: -24240px 0px; }
+.emoji-26D4 { background-position: -24260px 0px; }
+.emoji-26EA { background-position: -24280px 0px; }
+.emoji-26F2 { background-position: -24300px 0px; }
+.emoji-26F3 { background-position: -24320px 0px; }
+.emoji-26F5 { background-position: -24340px 0px; }
+.emoji-26FA { background-position: -24360px 0px; }
+.emoji-26FD { background-position: -24380px 0px; }
+.emoji-2702 { background-position: -24400px 0px; }
+.emoji-2705 { background-position: -24420px 0px; }
+.emoji-2708 { background-position: -24440px 0px; }
+.emoji-2709 { background-position: -24460px 0px; }
+.emoji-270A { background-position: -24480px 0px; }
+.emoji-270B { background-position: -24500px 0px; }
+.emoji-270C { background-position: -24520px 0px; }
+.emoji-270F { background-position: -24540px 0px; }
+.emoji-2712 { background-position: -24560px 0px; }
+.emoji-2714 { background-position: -24580px 0px; }
+.emoji-2716 { background-position: -24600px 0px; }
+.emoji-2728 { background-position: -24620px 0px; }
+.emoji-2733 { background-position: -24640px 0px; }
+.emoji-2734 { background-position: -24660px 0px; }
+.emoji-2744 { background-position: -24680px 0px; }
+.emoji-2747 { background-position: -24700px 0px; }
+.emoji-274C { background-position: -24720px 0px; }
+.emoji-274E { background-position: -24740px 0px; }
+.emoji-2753 { background-position: -24760px 0px; }
+.emoji-2754 { background-position: -24780px 0px; }
+.emoji-2755 { background-position: -24800px 0px; }
+.emoji-2757 { background-position: -24820px 0px; }
+.emoji-2764 { background-position: -24840px 0px; }
+.emoji-2795 { background-position: -24860px 0px; }
+.emoji-2796 { background-position: -24880px 0px; }
+.emoji-2797 { background-position: -24900px 0px; }
+.emoji-27A1 { background-position: -24920px 0px; }
+.emoji-27B0 { background-position: -24940px 0px; }
+.emoji-27BF { background-position: -24960px 0px; }
+.emoji-2934 { background-position: -24980px 0px; }
+.emoji-2935 { background-position: -25000px 0px; }
+.emoji-2B05 { background-position: -25020px 0px; }
+.emoji-2B06 { background-position: -25040px 0px; }
+.emoji-2B07 { background-position: -25060px 0px; }
+.emoji-2B1B { background-position: -25080px 0px; }
+.emoji-2B1C { background-position: -25100px 0px; }
+.emoji-2B50 { background-position: -25120px 0px; }
+.emoji-2B55 { background-position: -25140px 0px; }
+.emoji-3030 { background-position: -25160px 0px; }
+.emoji-303D { background-position: -25180px 0px; }
+.emoji-3297 { background-position: -25200px 0px; }
+.emoji-3299 { background-position: -25220px 0px; } \ No newline at end of file
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 797a0af3720..9da273a0b6b 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -18,7 +18,7 @@
&.affix {
position: fixed;
- top: 60px;
+ top: 70px;
margin-right: 35px;
}
}
@@ -36,33 +36,12 @@
}
.issuable-details {
- .issue-title {
- margin: 0;
- font-size: 23px;
- color: #313236;
- }
-
- .description {
- margin-top: 6px;
-
- p:last-child {
- margin-bottom: 0;
- }
- }
-
section {
- border-right: 1px solid #ECEEF1;
+ border-right: 1px solid $border-white-light;
- > .tab-content {
+ .issuable-discussion {
margin-right: 1px;
}
-
- .issue-discussion > .gray-content-block,
- > .gray-content-block {
- margin-top: 0;
- border-top: none;
- margin-right: -15px;
- }
}
}
@@ -136,21 +115,3 @@
margin-right: 2px;
}
}
-
-.issuable-title {
- margin: -$gl-padding;
- padding: 7px $gl-padding;
- margin-bottom: 0px;
- border-bottom: 1px solid $border-color;
- color: #5c5d5e;
- font-size: 16px;
- line-height: 42px;
-
- .author {
- color: #5c5d5e;
- }
-
- .issuable-id {
- color: #5c5d5e;
- }
-}
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index a652b65502f..a02a3a72e79 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -141,11 +141,6 @@ form.edit-issue {
}
}
-.issue-closed-by-widget {
- padding: 16px 0;
- margin: 0px;
-}
-
.issue-form .select2-container {
width: 250px !important;
}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 502e9552acd..82effde0bf3 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -191,7 +191,7 @@
.btn-clipboard {
@extend .pull-right;
- margin-right: 18px;
+ margin-right: 20px;
margin-top: 5px;
position: absolute;
right: 0;
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index e1a72af0013..d86259f93fb 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -75,17 +75,15 @@
.common-note-form {
margin: 0;
- background: #F7F8FA;
+ background: #fff;
padding: $gl-padding;
margin-left: -$gl-padding;
margin-right: -$gl-padding;
- border-right: 1px solid $border-color;
- border-top: 1px solid $border-color;
margin-bottom: -$gl-padding;
}
.note-form-actions {
- background: #F9F9F9;
+ background: #fff;
.note-form-option {
margin-top: 8px;
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 4dff87abaa4..72b0ed29a69 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -128,7 +128,7 @@ ul.notes {
}
&:last-child {
- border-bottom: none;
+ border-bottom: 1px solid $border-color;
}
}
}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 2ded32dba12..cff3edb7ed2 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -91,21 +91,83 @@
}
}
- .input-group {
+ .git-clone-holder {
display: inline-table;
position: relative;
- top: 17px;
}
.project-repo-buttons {
margin-top: 12px;
margin-bottom: 0px;
+ .count-buttons {
+ display: block;
+ margin-bottom: 12px;
+ }
+
.btn {
@include btn-gray;
-
+ text-transform: none;
+ }
+ .count-with-arrow {
+ display: inline-block;
+ position: relative;
+ margin-left: 4px;
+
+ .arrow {
+ &:before {
+ content: '';
+ display: inline-block;
+ position: absolute;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid;
+ top: 50%;
+ left: 0;
+ margin-top: -6px;
+ border-width: 7px 5px 7px 0;
+ border-right-color: #dce0e5;
+ }
+
+ &:after {
+ content: '';
+ position: absolute;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid;
+ top: 50%;
+ left: 1px;
+ margin-top: -9px;
+ border-width: 10px 7px 10px 0;
+ border-right-color: #FFF;
+ }
+ }
.count {
+ @include btn-gray;
display: inline-block;
+ background: white;
+ border-radius: 2px;
+ border-width: 1px;
+ border-style: solid;
+ font-size: 13px;
+ font-weight: 600;
+ line-height: 20px;
+ padding: 11px 16px;
+ letter-spacing: .4px;
+ padding: 10px;
+ text-align: center;
+ vertical-align: middle;
+ touch-action: manipulation;
+ cursor: pointer;
+ background-image: none;
+ white-space: nowrap;
+ margin: 0 11px 0px 4px;
+
+ &:hover {
+ background: #FFF;
+ }
}
}
}
@@ -125,6 +187,13 @@
margin-right: 45px;
}
+ .clone-options {
+ display: table-cell;
+ a.btn {
+ width: 100%;
+ }
+ }
+
.form-control {
cursor: auto;
@extend .monospace;
@@ -335,6 +404,38 @@ ul.nav.nav-projects-tabs {
}
}
+.top-area {
+ border-bottom: 1px solid #EEE;
+ margin: 0 -16px;
+ padding: 0 $gl-padding;
+
+ ul.left-top-menu {
+ display: inline-block;
+ width: 50%;
+ margin-bottom: 0px;
+ border-bottom: none;
+ }
+
+ .projects-search-form {
+ width: 50%;
+ display: inline-block;
+ float: right;
+ padding-top: 7px;
+ text-align: right;
+
+ .btn-green {
+ margin-top: -2px;
+ margin-left: 10px;
+ }
+ }
+
+ @media (max-width: $screen-xs-max) {
+ .projects-search-form {
+ padding-top: 15px;
+ }
+ }
+}
+
.fork-namespaces {
.fork-thumbnail {
text-align: center;
@@ -412,11 +513,18 @@ pre.light-well {
.projects-search-form {
margin: -$gl-padding;
- background-color: #f8fafc;
padding: $gl-padding;
margin-bottom: 0px;
- border-top: 1px solid #e7e9ed;
- border-bottom: 1px solid #e7e9ed;
+
+ input {
+ display: inline-block;
+ width: calc(100% - 151px);
+ }
+
+ .btn {
+ display: inline-block;
+ width: 135px;
+ }
}
.git-empty {
diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss
index a7d3b2197f1..4b6ef035673 100644
--- a/app/assets/stylesheets/pages/status.scss
+++ b/app/assets/stylesheets/pages/status.scss
@@ -35,3 +35,20 @@
border-color: $gl-warning;
}
}
+
+.ci-status-icon-success {
+ @extend .cgreen;
+}
+.ci-status-icon-failed {
+ @extend .cred;
+}
+.ci-status-icon-running,
+.ci-status-icon-pending {
+ // These are standard text color
+}
+.ci-status-icon-canceled,
+.ci-status-icon-disabled,
+.ci-status-icon-not-found,
+.ci-status-icon-skipped {
+ @extend .cgray;
+}
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 9dd16f8c735..10e736fd362 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -49,6 +49,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:default_branch_protection,
:signup_enabled,
:signin_enabled,
+ :require_two_factor_authentication,
+ :two_factor_grace_period,
:gravatar_enabled,
:twitter_sharing_enabled,
:sign_in_text,
@@ -65,6 +67,17 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:user_oauth_applications,
:shared_runners_enabled,
:max_artifacts_size,
+ :metrics_enabled,
+ :metrics_host,
+ :metrics_port,
+ :metrics_username,
+ :metrics_password,
+ :metrics_pool_size,
+ :metrics_timeout,
+ :metrics_method_call_threshold,
+ :recaptcha_enabled,
+ :recaptcha_site_key,
+ :recaptcha_private_key,
restricted_visibility_levels: [],
import_sources: []
)
diff --git a/app/controllers/admin/identities_controller.rb b/app/controllers/admin/identities_controller.rb
index d28614731f9..e383fe38ea6 100644
--- a/app/controllers/admin/identities_controller.rb
+++ b/app/controllers/admin/identities_controller.rb
@@ -1,6 +1,21 @@
class Admin::IdentitiesController < Admin::ApplicationController
before_action :user
- before_action :identity, except: :index
+ before_action :identity, except: [:index, :new, :create]
+
+ def new
+ @identity = Identity.new
+ end
+
+ def create
+ @identity = Identity.new(identity_params)
+ @identity.user_id = user.id
+
+ if @identity.save
+ redirect_to admin_user_identities_path(@user), notice: 'User identity was successfully created.'
+ else
+ render :new
+ end
+ end
def index
@identities = @user.identities
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 0d182e8eb04..d9a37a4d45f 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -10,8 +10,10 @@ class ApplicationController < ActionController::Base
before_action :authenticate_user_from_token!
before_action :authenticate_user!
+ before_action :validate_user_service_ticket!
before_action :reject_blocked!
before_action :check_password_expiration
+ before_action :check_2fa_requirement
before_action :ldap_security_check
before_action :default_headers
before_action :add_gon_variables
@@ -202,12 +204,32 @@ class ApplicationController < ActionController::Base
end
end
+ def validate_user_service_ticket!
+ return unless signed_in? && session[:service_tickets]
+
+ valid = session[:service_tickets].all? do |provider, ticket|
+ Gitlab::OAuth::Session.valid?(provider, ticket)
+ end
+
+ unless valid
+ session[:service_tickets] = nil
+ sign_out current_user
+ redirect_to new_user_session_path
+ end
+ end
+
def check_password_expiration
if current_user && current_user.password_expires_at && current_user.password_expires_at < Time.now && !current_user.ldap_user?
redirect_to new_profile_password_path and return
end
end
+ def check_2fa_requirement
+ if two_factor_authentication_required? && current_user && !current_user.two_factor_enabled && !skip_two_factor?
+ redirect_to new_profile_two_factor_auth_path
+ end
+ end
+
def ldap_security_check
if current_user && current_user.requires_ldap_check?
unless Gitlab::LDAP::Access.allowed?(current_user)
@@ -342,6 +364,23 @@ class ApplicationController < ActionController::Base
current_application_settings.import_sources.include?('git')
end
+ def two_factor_authentication_required?
+ current_application_settings.require_two_factor_authentication
+ end
+
+ def two_factor_grace_period
+ current_application_settings.two_factor_grace_period
+ end
+
+ def two_factor_grace_period_expired?
+ date = current_user.otp_grace_period_started_at
+ date && (date + two_factor_grace_period.hours) < Time.current
+ end
+
+ def skip_two_factor?
+ session[:skip_tfa] && session[:skip_tfa] > Time.current
+ 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/lints_controller.rb b/app/controllers/ci/lints_controller.rb
index 7ed78ff8e98..e782a51e7eb 100644
--- a/app/controllers/ci/lints_controller.rb
+++ b/app/controllers/ci/lints_controller.rb
@@ -19,8 +19,10 @@ module Ci
@error = e.message
@status = false
rescue
- @error = "Undefined error"
+ @error = 'Undefined error'
@status = false
+ ensure
+ render :show
end
end
end
diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb
new file mode 100644
index 00000000000..62127a09081
--- /dev/null
+++ b/app/controllers/concerns/creates_commit.rb
@@ -0,0 +1,103 @@
+module CreatesCommit
+ extend ActiveSupport::Concern
+
+ def create_commit(service, success_path:, failure_path:, failure_view: nil, success_notice: nil)
+ set_commit_variables
+
+ commit_params = @commit_params.merge(
+ source_project: @project,
+ source_branch: @ref,
+ target_branch: @target_branch
+ )
+
+ result = service.new(@tree_edit_project, current_user, commit_params).execute
+
+ if result[:status] == :success
+ flash[:notice] = success_notice || "Your changes have been successfully committed."
+
+ if create_merge_request?
+ success_path = new_merge_request_path
+ target = different_project? ? "project" : "branch"
+ flash[:notice] << " You can now submit a merge request to get this change into the original #{target}."
+ end
+
+ respond_to do |format|
+ format.html { redirect_to success_path }
+ format.json { render json: { message: "success", filePath: success_path } }
+ end
+ else
+ flash[:alert] = result[:message]
+ respond_to do |format|
+ format.html do
+ if failure_view
+ render failure_view
+ else
+ redirect_to failure_path
+ end
+ end
+ format.json { render json: { message: "failed", filePath: failure_path } }
+ end
+ end
+ end
+
+ def authorize_edit_tree!
+ return if can?(current_user, :push_code, project)
+ return if current_user && current_user.already_forked?(project)
+
+ access_denied!
+ end
+
+ private
+
+ def new_merge_request_path
+ new_namespace_project_merge_request_path(
+ @mr_source_project.namespace,
+ @mr_source_project,
+ merge_request: {
+ source_project_id: @mr_source_project.id,
+ target_project_id: @mr_target_project.id,
+ source_branch: @mr_source_branch,
+ target_branch: @mr_target_branch
+ }
+ )
+ end
+
+ def different_project?
+ @mr_source_project != @mr_target_project
+ end
+
+ def different_branch?
+ @mr_source_branch != @mr_target_branch || different_project?
+ end
+
+ def create_merge_request?
+ params[:create_merge_request].present? && different_branch?
+ end
+
+ def set_commit_variables
+ @mr_source_branch = @target_branch
+
+ if can?(current_user, :push_code, @project)
+ # Edit file in this project
+ @tree_edit_project = @project
+ @mr_source_project = @project
+
+ if @project.forked?
+ # Merge request from this project to fork origin
+ @mr_target_project = @project.forked_from_project
+ @mr_target_branch = @mr_target_project.repository.root_ref
+ else
+ # Merge request to this project
+ @mr_target_project = @project
+ @mr_target_branch = @ref
+ end
+ else
+ # Edit file in fork
+ @tree_edit_project = current_user.fork_of(@project)
+ # Merge request from fork to this project
+ @mr_source_project = @tree_edit_project
+ @mr_target_project = @project
+ @mr_target_branch = @mr_target_project.repository.root_ref
+ end
+ end
+end
diff --git a/app/controllers/concerns/creates_merge_request_for_commit.rb b/app/controllers/concerns/creates_merge_request_for_commit.rb
deleted file mode 100644
index c7527822158..00000000000
--- a/app/controllers/concerns/creates_merge_request_for_commit.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-module CreatesMergeRequestForCommit
- extend ActiveSupport::Concern
-
- def new_merge_request_path
- if @project.forked?
- target_project = @project.forked_from_project || @project
- target_branch = target_project.repository.root_ref
- else
- target_project = @project
- target_branch = @ref
- end
-
- new_namespace_project_merge_request_path(
- @project.namespace,
- @project,
- merge_request: {
- source_project_id: @project.id,
- target_project_id: target_project.id,
- source_branch: @new_branch,
- target_branch: target_branch
- }
- )
- end
-
- def create_merge_request?
- params[:create_merge_request] && @new_branch != @ref
- end
-end
diff --git a/app/controllers/dashboard/snippets_controller.rb b/app/controllers/dashboard/snippets_controller.rb
index f4354c6d8ca..b3594d82530 100644
--- a/app/controllers/dashboard/snippets_controller.rb
+++ b/app/controllers/dashboard/snippets_controller.rb
@@ -1,6 +1,7 @@
class Dashboard::SnippetsController < Dashboard::ApplicationController
def index
- @snippets = SnippetsFinder.new.execute(current_user,
+ @snippets = SnippetsFinder.new.execute(
+ current_user,
filter: :by_user,
user: current_user,
scope: params[:scope]
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index f809fa7500a..4cad98b8e98 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -1,6 +1,6 @@
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
- protect_from_forgery except: [:kerberos, :saml]
+ protect_from_forgery except: [:kerberos, :saml, :cas3]
Gitlab.config.omniauth.providers.each do |provider|
define_method provider['name'] do
@@ -42,6 +42,14 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
render 'errors/omniauth_error', layout: "errors", status: 422
end
+ def cas3
+ ticket = params['ticket']
+ if ticket
+ handle_service_ticket oauth['provider'], ticket
+ end
+ handle_omniauth
+ end
+
private
def handle_omniauth
@@ -84,6 +92,12 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
redirect_to new_user_session_path
end
+ def handle_service_ticket provider, ticket
+ Gitlab::OAuth::Session.create provider, ticket
+ session[:service_tickets] ||= {}
+ session[:service_tickets][provider] = ticket
+ end
+
def oauth
@oauth ||= request.env['omniauth.auth']
end
diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb
index e6b99be37fb..6e91d9b4ad9 100644
--- a/app/controllers/profiles/two_factor_auths_controller.rb
+++ b/app/controllers/profiles/two_factor_auths_controller.rb
@@ -1,8 +1,22 @@
class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
+ skip_before_action :check_2fa_requirement
+
def new
unless current_user.otp_secret
current_user.otp_secret = User.generate_otp_secret(32)
- current_user.save!
+ end
+
+ unless current_user.otp_grace_period_started_at && two_factor_grace_period
+ current_user.otp_grace_period_started_at = Time.current
+ end
+
+ current_user.save! if current_user.changed?
+
+ if two_factor_grace_period_expired?
+ flash.now[:alert] = 'You must configure Two-Factor Authentication in your account.'
+ else
+ grace_period_deadline = current_user.otp_grace_period_started_at + two_factor_grace_period.hours
+ flash.now[:alert] = "You must configure Two-Factor Authentication in your account until #{l(grace_period_deadline)}."
end
@qr_code = build_qr_code
@@ -34,6 +48,15 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
redirect_to profile_account_path
end
+ def skip
+ if two_factor_grace_period_expired?
+ redirect_to new_profile_two_factor_auth_path, alert: 'Cannot skip two factor authentication setup'
+ else
+ session[:skip_tfa] = current_user.otp_grace_period_started_at + two_factor_grace_period.hours
+ redirect_to root_path
+ end
+ end
+
private
def build_qr_code
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 62163682936..c56a3497bb2 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -1,7 +1,7 @@
# Controller for viewing a file's blame
class Projects::BlobController < Projects::ApplicationController
include ExtractsPath
- include CreatesMergeRequestForCommit
+ include CreatesCommit
include ActionView::Helpers::SanitizeHelper
# Raised when given an invalid file path
@@ -9,21 +9,21 @@ class Projects::BlobController < Projects::ApplicationController
before_action :require_non_empty_project, except: [:new, :create]
before_action :authorize_download_code!
- before_action :authorize_push_code!, only: [:destroy, :create]
+ before_action :authorize_edit_tree!, only: [:new, :create, :edit, :update, :destroy]
before_action :assign_blob_vars
before_action :commit, except: [:new, :create]
before_action :blob, except: [:new, :create]
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 :after_edit_path, only: [:edit, :update]
def new
commit unless @repository.empty?
end
def create
- create_commit(Files::CreateService, success_path: after_create_path,
+ create_commit(Files::CreateService, success_notice: "The file has been successfully created.",
+ success_path: namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)),
failure_view: :new,
failure_path: namespace_project_new_blob_path(@project.namespace, @project, @ref))
end
@@ -36,6 +36,14 @@ class Projects::BlobController < Projects::ApplicationController
end
def update
+ 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) +
+ "#file-path-#{hexdigest(@path)}"
+ else
+ namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @path))
+ end
+
create_commit(Files::UpdateService, success_path: after_edit_path,
failure_view: :edit,
failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
@@ -50,15 +58,10 @@ class Projects::BlobController < Projects::ApplicationController
end
def destroy
- result = Files::DeleteService.new(@project, current_user, @commit_params).execute
-
- if result[:status] == :success
- flash[:notice] = "Your changes have been successfully committed"
- redirect_to after_destroy_path
- else
- flash[:alert] = result[:message]
- render :show
- end
+ create_commit(Files::DeleteService, success_notice: "The file has been successfully deleted.",
+ success_path: namespace_project_tree_path(@project.namespace, @project, @target_branch),
+ failure_view: :show,
+ failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
end
def diff
@@ -108,74 +111,13 @@ class Projects::BlobController < Projects::ApplicationController
render_404
end
- def create_commit(service, success_path:, failure_view:, failure_path:)
- result = service.new(@project, current_user, @commit_params).execute
-
- if result[:status] == :success
- flash[:notice] = "Your changes have been successfully committed"
- respond_to do |format|
- format.html { redirect_to success_path }
- format.json { render json: { message: "success", filePath: success_path } }
- end
- else
- flash[:alert] = result[:message]
- respond_to do |format|
- format.html { render failure_view }
- format.json { render json: { message: "failed", filePath: failure_path } }
- end
- end
- end
-
- def after_create_path
- @after_create_path ||=
- if create_merge_request?
- new_merge_request_path
- else
- namespace_project_blob_path(@project.namespace, @project, File.join(@new_branch, @file_path))
- end
- end
-
- def after_edit_path
- @after_edit_path ||=
- if create_merge_request?
- new_merge_request_path
- elsif from_merge_request && @new_branch == @ref
- diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) +
- "#file-path-#{hexdigest(@path)}"
- else
- namespace_project_blob_path(@project.namespace, @project, File.join(@new_branch, @path))
- end
- end
-
- def after_destroy_path
- @after_destroy_path ||=
- if create_merge_request?
- new_merge_request_path
- else
- namespace_project_tree_path(@project.namespace, @project, @new_branch)
- end
- end
-
def from_merge_request
# If blob edit was initiated from merge request page
@from_merge_request ||= MergeRequest.find_by(id: params[:from_merge_request_id])
end
- def sanitized_new_branch_name
- sanitize(strip_tags(params[:new_branch]))
- end
-
def editor_variables
- @current_branch = @ref
-
- @new_branch =
- if params[:new_branch].present?
- sanitized_new_branch_name
- elsif ::Gitlab::GitAccess.new(current_user, @project).can_push_to_branch?(@ref)
- @ref
- else
- @repository.next_patch_branch
- end
+ @target_branch = params[:target_branch]
@file_path =
if action_name.to_s == 'create'
@@ -194,8 +136,6 @@ class Projects::BlobController < Projects::ApplicationController
@commit_params = {
file_path: @file_path,
- current_branch: @current_branch,
- target_branch: @new_branch,
commit_message: params[:commit_message],
file_content: params[:content],
file_content_encoding: params[:encoding]
diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb
index 58fb946dbc2..04a88990bf4 100644
--- a/app/controllers/projects/commits_controller.rb
+++ b/app/controllers/projects/commits_controller.rb
@@ -9,7 +9,7 @@ class Projects::CommitsController < Projects::ApplicationController
def show
@repo = @project.repository
- @limit, @offset = (params[:limit] || 40), (params[:offset] || 0)
+ @limit, @offset = (params[:limit] || 40).to_i, (params[:offset] || 0).to_i
@commits = @repo.commits(@ref, @path, @limit, @offset)
@note_counts = project.notes.where(commit_id: @commits.map(&:id)).
diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb
index 8a785076bb7..750181f0c19 100644
--- a/app/controllers/projects/forks_controller.rb
+++ b/app/controllers/projects/forks_controller.rb
@@ -10,19 +10,35 @@ class Projects::ForksController < Projects::ApplicationController
def create
namespace = Namespace.find(params[:namespace_key])
- @forked_project = ::Projects::ForkService.new(project, current_user, namespace: namespace).execute
+
+ @forked_project = namespace.projects.find_by(path: project.path)
+ @forked_project = nil unless @forked_project && @forked_project.forked_from_project == project
+
+ @forked_project ||= ::Projects::ForkService.new(project, current_user, namespace: namespace).execute
if @forked_project.saved? && @forked_project.forked?
if @forked_project.import_in_progress?
- redirect_to namespace_project_import_path(@forked_project.namespace, @forked_project)
+ redirect_to namespace_project_import_path(@forked_project.namespace, @forked_project, continue: continue_params)
else
- redirect_to(
- namespace_project_path(@forked_project.namespace, @forked_project),
- notice: 'Project was successfully forked.'
- )
+ if continue_params
+ redirect_to continue_params[:to], notice: continue_params[:notice]
+ else
+ redirect_to namespace_project_path(@forked_project.namespace, @forked_project), notice: "The project was successfully forked."
+ end
end
else
render :error
end
end
+
+ private
+
+ def continue_params
+ continue_params = params[:continue]
+ if continue_params
+ continue_params.permit(:to, :notice, :notice_now)
+ else
+ nil
+ end
+ end
end
diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb
index fb8788f0818..8d8035ef5ff 100644
--- a/app/controllers/projects/imports_controller.rb
+++ b/app/controllers/projects/imports_controller.rb
@@ -1,7 +1,7 @@
class Projects::ImportsController < Projects::ApplicationController
# Authorize
before_action :authorize_admin_project!
- before_action :require_no_repo
+ before_action :require_no_repo, except: :show
before_action :redirect_if_progress, except: :show
def new
@@ -24,21 +24,36 @@ class Projects::ImportsController < Projects::ApplicationController
end
def show
- unless @project.import_in_progress?
- if @project.import_finished?
- redirect_to(project_path(@project)) and return
+ if @project.repository_exists? || @project.import_finished?
+ if continue_params
+ redirect_to continue_params[:to], notice: continue_params[:notice]
else
- redirect_to(new_namespace_project_import_path(@project.namespace,
- @project)) and return
+ redirect_to project_path(@project), notice: "The project was successfully forked."
end
+ elsif @project.import_failed?
+ redirect_to new_namespace_project_import_path(@project.namespace, @project)
+ else
+ if continue_params && continue_params[:notice_now]
+ flash.now[:notice] = continue_params[:notice_now]
+ end
+ # Render
end
end
private
+ def continue_params
+ continue_params = params[:continue]
+ if continue_params
+ continue_params.permit(:to, :notice, :notice_now)
+ else
+ nil
+ end
+ end
+
def require_no_repo
if @project.repository_exists? && !@project.import_in_progress?
- redirect_to(namespace_project_path(@project.namespace, @project)) and return
+ redirect_to(namespace_project_path(@project.namespace, @project))
end
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index fffd90d87eb..ab5c953189c 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -7,7 +7,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
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]
+ before_action :define_widget_vars, only: [:merge, :cancel_merge_when_build_succeeds, :merge_check]
before_action :ensure_ref_fetched, only: [:show, :diffs, :commits, :builds]
# Allow read any merge_request
@@ -153,11 +153,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def merge_check
- if @merge_request.unchecked?
- @merge_request.check_if_can_be_merged
- end
-
- closes_issues
+ @merge_request.check_if_can_be_merged if @merge_request.unchecked?
render partial: "projects/merge_requests/widget/show.html.haml", layout: false
end
@@ -178,7 +174,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request.update(merge_error: nil)
- if params[:merge_when_build_succeeds] && @merge_request.ci_commit && @merge_request.ci_commit.active?
+ if params[:merge_when_build_succeeds].present? && @merge_request.ci_commit && @merge_request.ci_commit.active?
MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params)
.execute(@merge_request)
@status = :merge_when_build_succeeds
@@ -299,6 +295,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def define_widget_vars
@ci_commit = @merge_request.ci_commit
+ closes_issues
end
def invalid_mr
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index ae6e9f6fd38..6f1e186d408 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -69,7 +69,7 @@ class Projects::NotesController < Projects::ApplicationController
data = {
author: current_user,
is_award: true,
- note: note_params[:note].gsub(":", '')
+ note: note_params[:note].delete(":")
}
note = noteable.notes.find_by(data)
@@ -139,7 +139,6 @@ class Projects::NotesController < Projects::ApplicationController
discussion_id: note.discussion_id,
html: note_to_html(note),
award: note.is_award,
- emoji_path: note.is_award ? view_context.image_url(::AwardEmoji.path_to_emoji_image(note.note)) : "",
note: note.note,
discussion_html: note_to_discussion_html(note),
discussion_with_diff_html: note_to_discussion_with_diff_html(note)
diff --git a/app/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb
index 6b52eccebf7..e49259c34b6 100644
--- a/app/controllers/projects/protected_branches_controller.rb
+++ b/app/controllers/projects/protected_branches_controller.rb
@@ -21,7 +21,7 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController
if protected_branch &&
protected_branch.update_attributes(
- developers_can_push: params[:developers_can_push]
+ developers_can_push: params[:developers_can_push]
)
respond_to do |format|
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index 6e7590260ff..8b2577aebe1 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -1,5 +1,5 @@
class Projects::ServicesController < Projects::ApplicationController
- ALLOWED_PARAMS = [:title, :token, :type, :active, :api_key, :api_version, :subdomain,
+ ALLOWED_PARAMS = [:title, :token, :type, :active, :api_key, :api_url, :api_version, :subdomain,
:room, :recipients, :project_url, :webhook,
:user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
:build_key, :server, :teamcity_url, :drone_url, :build_type,
@@ -10,7 +10,8 @@ class Projects::ServicesController < Projects::ApplicationController
:notify_only_broken_builds, :add_pusher,
:send_from_committer_email, :disable_diffs, :external_wiki_url,
:notify, :color,
- :server_host, :server_port, :default_irc_uri, :enable_ssl_verification]
+ :server_host, :server_port, :default_irc_uri, :enable_ssl_verification,
+ :jira_issue_transition_id]
# Parameters to ignore if no value is specified
FILTER_BLANK_PARAMS = [:password]
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index 8f272ad1281..cb3ed0f6f9c 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -1,14 +1,14 @@
# Controller for viewing a repository's file structure
class Projects::TreeController < Projects::ApplicationController
include ExtractsPath
- include CreatesMergeRequestForCommit
+ include CreatesCommit
include ActionView::Helpers::SanitizeHelper
before_action :require_non_empty_project, except: [:new, :create]
before_action :assign_ref_vars
before_action :assign_dir_vars, only: [:create_dir]
before_action :authorize_download_code!
- before_action :authorize_push_code!, only: [:create_dir]
+ before_action :authorize_edit_tree!, only: [:create_dir]
def show
return render_404 unless @repository.commit(@ref)
@@ -34,44 +34,20 @@ class Projects::TreeController < Projects::ApplicationController
def create_dir
return render_404 unless @commit_params.values.all?
- begin
- result = Files::CreateDirService.new(@project, current_user, @commit_params).execute
- message = result[:message]
- rescue => e
- message = e.to_s
- end
-
- if result && result[:status] == :success
- flash[:notice] = "The directory has been successfully created"
- respond_to do |format|
- format.html { redirect_to after_create_dir_path }
- end
- else
- flash[:alert] = message
- respond_to do |format|
- format.html { redirect_to namespace_project_blob_path(@project.namespace, @project, @new_branch) }
- end
- end
+ create_commit(Files::CreateDirService, success_notice: "The directory has been successfully created.",
+ success_path: namespace_project_tree_path(@project.namespace, @project, File.join(@target_branch, @dir_name)),
+ failure_path: namespace_project_tree_path(@project.namespace, @project, @ref))
end
private
def assign_dir_vars
- @new_branch = params[:new_branch].present? ? sanitize(strip_tags(params[:new_branch])) : @ref
+ @target_branch = params[:target_branch]
+
@dir_name = File.join(@path, params[:dir_name])
@commit_params = {
file_path: @dir_name,
- current_branch: @ref,
- target_branch: @new_branch,
commit_message: params[:commit_message],
}
end
-
- def after_create_dir_path
- if create_merge_request?
- new_merge_request_path
- else
- namespace_project_blob_path(@project.namespace, @project, File.join(@new_branch, @dir_name))
- end
- end
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index bf5e25ff895..3004722bce0 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -171,14 +171,14 @@ class ProjectsController < ApplicationController
@project.reload
render json: {
- html: view_to_html_string("projects/buttons/_star")
+ star_count: @project.star_count
}
end
def markdown_preview
text = params[:text]
- ext = Gitlab::ReferenceExtractor.new(@project, current_user)
+ ext = Gitlab::ReferenceExtractor.new(@project, current_user, current_user)
ext.analyze(text)
render json: {
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index 3b3dc86cb68..c48175a4c5a 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -1,10 +1,21 @@
class RegistrationsController < Devise::RegistrationsController
before_action :signup_enabled?
+ include Recaptcha::Verify
def new
redirect_to(new_user_session_path)
end
+ def create
+ if !Gitlab::Recaptcha.load_configurations! || verify_recaptcha
+ super
+ else
+ flash[:alert] = "There was an error with the reCAPTCHA code below. Please re-enter the code."
+ flash.delete :recaptcha_error
+ render action: 'new'
+ end
+ end
+
def destroy
DeleteUserService.new(current_user).execute(current_user)
@@ -38,4 +49,16 @@ class RegistrationsController < Devise::RegistrationsController
def sign_up_params
params.require(:user).permit(:username, :email, :name, :password, :password_confirmation)
end
+
+ def resource_name
+ :user
+ end
+
+ def resource
+ @resource ||= User.new(sign_up_params)
+ end
+
+ def devise_mapping
+ @devise_mapping ||= Devise.mappings[:user]
+ end
end
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 1b60d3e27d0..825f85199be 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -1,9 +1,11 @@
class SessionsController < Devise::SessionsController
include AuthenticatesWithTwoFactor
+ include Recaptcha::ClientHelper
prepend_before_action :authenticate_with_two_factor, only: [:create]
prepend_before_action :store_redirect_path, only: [:new]
before_action :auto_sign_in_with_provider, only: [:new]
+ before_action :load_recaptcha
def new
if Gitlab.config.ldap.enabled
@@ -40,7 +42,7 @@ class SessionsController < Devise::SessionsController
User.find(session[:otp_user_id])
end
end
-
+
def store_redirect_path
redirect_path =
if request.referer.present? && (params['redirect_to_referer'] == 'yes')
@@ -87,14 +89,14 @@ class SessionsController < Devise::SessionsController
provider = Gitlab.config.omniauth.auto_sign_in_with_provider
return unless provider.present?
- # Auto sign in with an Omniauth provider only if the standard "you need to sign-in" alert is
- # registered or no alert at all. In case of another alert (such as a blocked user), it is safer
+ # Auto sign in with an Omniauth provider only if the standard "you need to sign-in" alert is
+ # registered or no alert at all. In case of another alert (such as a blocked user), it is safer
# to do nothing to prevent redirection loops with certain Omniauth providers.
return unless flash[:alert].blank? || flash[:alert] == I18n.t('devise.failure.unauthenticated')
-
+
# Prevent alert from popping up on the first page shown after authentication.
- flash[:alert] = nil
-
+ flash[:alert] = nil
+
redirect_to user_omniauth_authorize_path(provider.to_sym)
end
@@ -107,4 +109,8 @@ class SessionsController < Devise::SessionsController
AuditEventService.new(user, user, options).
for_authentication.security_event
end
+
+ def load_recaptcha
+ Gitlab::Recaptcha.load_configurations!
+ end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 21f962df206..f7f7a1a02d3 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -61,7 +61,7 @@ module ApplicationHelper
options[:class] ||= ''
options[:class] << ' identicon'
bg_key = project.id % 7
- style = "background-color: ##{ allowed_colors.values[bg_key] }; color: #555"
+ style = "background-color: ##{allowed_colors.values[bg_key]}; color: #555"
content_tag(:div, class: options[:class], style: style) do
project.name[0, 1].upcase
@@ -72,7 +72,7 @@ module ApplicationHelper
if user_or_email.is_a?(User)
user = user_or_email
else
- user = User.find_by(email: user_or_email)
+ user = User.find_by(email: user_or_email.downcase)
end
if user
@@ -204,12 +204,16 @@ module ApplicationHelper
# Returns an HTML-safe String
def time_ago_with_tooltip(time, placement: 'top', html_class: 'time_ago', skip_js: false)
element = content_tag :time, time.to_s,
- class: "#{html_class} js-timeago",
+ class: "#{html_class} js-timeago js-timeago-pending",
datetime: time.getutc.iso8601,
title: time.in_time_zone.stamp('Aug 21, 2011 9:23pm'),
data: { toggle: 'tooltip', placement: placement, container: 'body' }
- element += javascript_tag "$('.js-timeago').last().timeago()" unless skip_js
+ unless skip_js
+ element << javascript_tag(
+ "$('.js-timeago-pending').removeClass('js-timeago-pending').timeago()"
+ )
+ end
element
end
diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb
index 2c81ea1623c..0cfc0565e84 100644
--- a/app/helpers/auth_helper.rb
+++ b/app/helpers/auth_helper.rb
@@ -50,5 +50,17 @@ module AuthHelper
current_user.identities.exists?(provider: provider.to_s)
end
+ def two_factor_skippable?
+ current_application_settings.require_two_factor_authentication &&
+ !current_user.two_factor_enabled &&
+ current_application_settings.two_factor_grace_period &&
+ !two_factor_grace_period_expired?
+ end
+
+ def two_factor_grace_period_expired?
+ current_user.otp_grace_period_started_at &&
+ (current_user.otp_grace_period_started_at + current_application_settings.two_factor_grace_period.hours) < Time.current
+ end
+
extend self
end
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 68e5d5be600..d31d4cde08f 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -22,32 +22,90 @@ module BlobHelper
%w(credits changelog news copying copyright license authors)
end
- def edit_blob_link(project, ref, path, options = {})
- blob =
- begin
- project.repository.blob_at(ref, path)
- rescue
- nil
- end
-
- return unless blob && blob.text? && blob_editable?(blob)
-
- text = 'Edit'
- after = options[:after] || ''
+ def edit_blob_link(project = @project, ref = @ref, path = @path, options = {})
+ return unless current_user
+
+ blob = project.repository.blob_at(ref, path) rescue nil
+
+ return unless blob && blob_text_viewable?(blob)
+
from_mr = options[:from_merge_request_id]
link_opts = {}
link_opts[:from_merge_request_id] = from_mr if from_mr
- cls = 'btn btn-small'
- link_to(text,
- namespace_project_edit_blob_path(project.namespace, project,
- tree_join(ref, path),
- link_opts),
- class: cls
- ) + after.html_safe
+
+ edit_path = namespace_project_edit_blob_path(project.namespace, project,
+ tree_join(ref, path),
+ link_opts)
+
+ if !on_top_of_branch?
+ button_tag "Edit", class: "btn btn-default disabled has_tooltip", title: "You can only edit files when you are on a branch", data: { container: 'body' }
+ elsif can_edit_blob?(blob)
+ link_to "Edit", edit_path, class: 'btn btn-small'
+ elsif can?(current_user, :fork_project, project)
+ continue_params = {
+ to: edit_path,
+ notice: edit_in_new_fork_notice,
+ notice_now: edit_in_new_fork_notice_now
+ }
+ fork_path = namespace_project_fork_path(project.namespace, project, namespace_key: current_user.namespace.id,
+ continue: continue_params)
+
+ link_to "Edit", fork_path, class: 'btn btn-small', method: :post
+ end
+ end
+
+ def modify_file_link(project = @project, ref = @ref, path = @path, label:, action:, btn_class:, modal_type:)
+ return unless current_user
+
+ blob = project.repository.blob_at(ref, path) rescue nil
+
+ return unless blob
+
+ if !on_top_of_branch?
+ button_tag label, class: "btn btn-#{btn_class} disabled has_tooltip", title: "You can only #{action} files when you are on a branch", data: { container: 'body' }
+ elsif blob.lfs_pointer?
+ button_tag label, class: "btn btn-#{btn_class} disabled has_tooltip", title: "It is not possible to #{action} files that are stored in LFS using the web interface", data: { container: 'body' }
+ elsif can_edit_blob?(blob)
+ button_tag label, class: "btn btn-#{btn_class}", 'data-target' => "#modal-#{modal_type}-blob", 'data-toggle' => 'modal'
+ elsif can?(current_user, :fork_project, project)
+ continue_params = {
+ to: request.fullpath,
+ notice: edit_in_new_fork_notice + " Try to #{action} this file again.",
+ notice_now: edit_in_new_fork_notice_now
+ }
+ fork_path = namespace_project_fork_path(project.namespace, project, namespace_key: current_user.namespace.id,
+ continue: continue_params)
+
+ link_to label, fork_path, class: "btn btn-#{btn_class}", method: :post
+ end
+ end
+
+ def replace_blob_link(project = @project, ref = @ref, path = @path)
+ modify_file_link(
+ project,
+ ref,
+ path,
+ label: "Replace",
+ action: "replace",
+ btn_class: "default",
+ modal_type: "upload"
+ )
+ end
+
+ def delete_blob_link(project = @project, ref = @ref, path = @path)
+ modify_file_link(
+ project,
+ ref,
+ path,
+ label: "Delete",
+ action: "delete",
+ btn_class: "remove",
+ modal_type: "remove"
+ )
end
- def blob_editable?(blob, project = @project, ref = @ref)
- !blob.lfs_pointer? && allowed_tree_edit?(project, ref)
+ def can_edit_blob?(blob, project = @project, ref = @ref)
+ !blob.lfs_pointer? && can_edit_tree?(project, ref)
end
def leave_edit_message
@@ -70,7 +128,7 @@ module BlobHelper
icon("#{file_type_icon_class('file', mode, name)} fw")
end
- def blob_viewable?(blob)
+ def blob_text_viewable?(blob)
blob && blob.text? && !blob.lfs_pointer?
end
diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb
index 313b6dde910..ec0e3f409c1 100644
--- a/app/helpers/button_helper.rb
+++ b/app/helpers/button_helper.rb
@@ -10,8 +10,8 @@ module ButtonHelper
# # => "<button class='...' data-clipboard-text='Foo'>...</button>"
#
# # Define the target element
- # clipboard_button(clipboard_target: "#foo")
- # # => "<button class='...' data-clipboard-target='#foo'>...</button>"
+ # clipboard_button(clipboard_target: "div#foo")
+ # # => "<button class='...' data-clipboard-target='div#foo'>...</button>"
#
# See http://clipboardjs.com/#usage
def clipboard_button(data = {})
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index 8554074d619..d8bee21c82e 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -12,19 +12,6 @@ module CiStatusHelper
ci_label_for_status(ci_commit.status)
end
- def ci_status_color(ci_commit)
- case ci_commit.status
- when 'success'
- 'green'
- when 'failed'
- 'red'
- when 'running', 'pending'
- 'yellow'
- else
- 'gray'
- end
- end
-
def ci_status_with_icon(status)
content_tag :span, class: "ci-status ci-#{status}" do
ci_icon_for_status(status) + '&nbsp;'.html_safe + ci_label_for_status(status)
@@ -56,12 +43,11 @@ module CiStatusHelper
end
def render_ci_status(ci_commit)
- link_to ci_status_path(ci_commit),
- class: "ci-status-link c#{ci_status_color(ci_commit)}",
+ link_to ci_status_icon(ci_commit),
+ ci_status_path(ci_commit),
+ class: "ci-status-link ci-status-icon-#{ci_commit.status.dasherize}",
title: "Build #{ci_status_label(ci_commit)}",
- data: { toggle: 'tooltip', placement: 'left' } do
- ci_status_icon(ci_commit)
- end
+ data: { toggle: 'tooltip', placement: 'left' }
end
def no_runners_for_project?(project)
diff --git a/app/helpers/external_wiki_helper.rb b/app/helpers/external_wiki_helper.rb
index 838b85afdfe..1f3401f2906 100644
--- a/app/helpers/external_wiki_helper.rb
+++ b/app/helpers/external_wiki_helper.rb
@@ -1,7 +1,7 @@
module ExternalWikiHelper
def get_project_wiki_path(project)
external_wiki_service = project.services.
- select { |service| service.to_param == 'external_wiki' }.first
+ find { |service| service.to_param == 'external_wiki' }
if external_wiki_service.present? && external_wiki_service.active?
external_wiki_service.properties['external_wiki_url']
else
diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
index a0cf3dc0843..ca41657cec1 100644
--- a/app/helpers/gitlab_markdown_helper.rb
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -65,7 +65,8 @@ module GitlabMarkdownHelper
end
def asciidoc(text)
- Gitlab::Asciidoc.render(text,
+ Gitlab::Asciidoc.render(
+ text,
project: @project,
current_user: (current_user if defined?(current_user)),
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index cdf7038b2f2..80e2741b09a 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -57,18 +57,22 @@ module IssuesHelper
options_from_collection_for_select(milestones, 'id', 'title', object.milestone_id)
end
- def issue_box_class(item)
+ def status_box_class(item)
if item.respond_to?(:expired?) && item.expired?
- 'issue-box-expired'
+ 'status-box-expired'
elsif item.respond_to?(:merged?) && item.merged?
- 'issue-box-merged'
+ 'status-box-merged'
elsif item.closed?
- 'issue-box-closed'
+ 'status-box-closed'
else
- 'issue-box-open'
+ 'status-box-open'
end
end
+ def issue_button_visibility(issue, closed)
+ return 'hidden' if issue.closed? == closed
+ end
+
def issue_to_atom(xml, issue)
xml.entry do
xml.id namespace_project_issue_url(issue.project.namespace,
@@ -94,11 +98,14 @@ module IssuesHelper
end.sort.to_sentence(last_word_connector: ', or ')
end
- def url_to_emoji(name)
- emoji_path = ::AwardEmoji.path_to_emoji_image(name)
- url_to_image(emoji_path)
- rescue StandardError
- ""
+ def emoji_icon(name, unicode = nil, aliases = [])
+ unicode ||= Emoji.emoji_filename(name)
+
+ content_tag :div, "",
+ class: "icon emoji-icon emoji-#{unicode}",
+ "data-emoji" => name,
+ "data-aliases" => aliases.join(" "),
+ "data-unicode-name" => unicode
end
def emoji_author_list(notes, current_user)
@@ -109,10 +116,6 @@ module IssuesHelper
list.join(", ")
end
- def emoji_list
- ::AwardEmoji::EMOJI_LIST
- end
-
def note_active_class(notes, current_user)
if current_user && notes.pluck(:author_id).include?(current_user.id)
"active"
@@ -121,6 +124,18 @@ module IssuesHelper
end
end
+ def awards_sort(awards)
+ awards.sort_by do |award, notes|
+ if award == "thumbsup"
+ 0
+ elsif award == "thumbsdown"
+ 1
+ else
+ 2
+ end
+ end.to_h
+ end
+
# Required for Banzai::Filter::IssueReferenceFilter
module_function :url_for_issue
end
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index 6c32647594d..1dd07a2a220 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -27,7 +27,16 @@ module MergeRequestsHelper
end
def ci_build_details_path(merge_request)
- 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.last_commit.sha, merge_request.source_branch)
+ return nil unless build_url
+
+ parsed_url = URI.parse(build_url)
+
+ unless parsed_url.userinfo.blank?
+ parsed_url.userinfo = ''
+ end
+
+ parsed_url.to_s
end
def merge_path_description(merge_request, separator)
diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb
index 9bf750124b2..791cb9e50bd 100644
--- a/app/helpers/page_layout_helper.rb
+++ b/app/helpers/page_layout_helper.rb
@@ -8,6 +8,80 @@ module PageLayoutHelper
@page_title.join(" \u00b7 ")
end
+ # Define or get a description for the current page
+ #
+ # description - String (default: nil)
+ #
+ # If this helper is called multiple times with an argument, only the last
+ # description will be returned when called without an argument. Descriptions
+ # have newlines replaced with spaces and all HTML tags are sanitized.
+ #
+ # Examples:
+ #
+ # page_description # => "GitLab Community Edition"
+ # page_description("Foo")
+ # page_description # => "Foo"
+ #
+ # page_description("<b>Bar</b>\nBaz")
+ # page_description # => "Bar Baz"
+ #
+ # Returns an HTML-safe String.
+ def page_description(description = nil)
+ @page_description ||= page_description_default
+
+ if description.present?
+ @page_description = description.squish
+ else
+ sanitize(@page_description, tags: []).truncate_words(30)
+ end
+ end
+
+ # Default value for page_description when one hasn't been defined manually by
+ # a view
+ def page_description_default
+ if @project
+ @project.description || brand_title
+ else
+ brand_title
+ end
+ end
+
+ def page_image
+ default = image_url('gitlab_logo.png')
+
+ if @project
+ @project.avatar_url || default
+ elsif @user
+ avatar_icon(@user)
+ else
+ default
+ end
+ end
+
+ # Define or get attributes to be used as Twitter card metadata
+ #
+ # map - Hash of label => data pairs. Keys become labels, values become data
+ #
+ # Raises ArgumentError if given more than two attributes
+ def page_card_attributes(map = {})
+ 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
+ end
+
+ def page_card_meta_tags
+ tags = ''
+
+ page_card_attributes.each_with_index do |pair, i|
+ tags << tag(:meta, property: "twitter:label#{i + 1}", content: pair[0])
+ tags << tag(:meta, property: "twitter:data#{i + 1}", content: pair[1])
+ end
+
+ tags.html_safe
+ end
+
def header_title(title = nil, title_url = nil)
if title
@header_title = title
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index d061136b7b8..77ba612548a 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -105,6 +105,14 @@ module ProjectsHelper
end
end
+ def user_max_access_in_project(user_id, project)
+ level = project.team.max_member_access(user_id)
+
+ if level
+ Gitlab::Access.options_with_owner.key(level)
+ end
+ end
+
private
def get_project_nav_tabs(project, current_user)
@@ -277,14 +285,6 @@ module ProjectsHelper
end
end
- def user_max_access_in_project(user, project)
- level = project.team.max_member_access(user)
-
- if level
- Gitlab::Access.options_with_owner.key(level)
- end
- end
-
def leave_project_message(project)
"Are you sure you want to leave \"#{project.name}\" project?"
end
@@ -330,10 +330,9 @@ module ProjectsHelper
def filename_path(project, filename)
if project && blob = project.repository.send(filename)
namespace_project_blob_path(
- project.namespace,
- project,
- tree_join(project.default_branch,
- blob.name)
+ project.namespace,
+ project,
+ tree_join(project.default_branch, blob.name)
)
end
end
diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb
index 886a1e734b5..2ad7c80dae0 100644
--- a/app/helpers/tree_helper.rb
+++ b/app/helpers/tree_helper.rb
@@ -50,24 +50,49 @@ module TreeHelper
project.repository.branch_names.include?(ref)
end
- def allowed_tree_edit?(project = nil, ref = nil)
+ def can_edit_tree?(project = nil, ref = nil)
project ||= @project
ref ||= @ref
+
return false unless on_top_of_branch?(project, ref)
- can?(current_user, :push_code, project)
+ can?(current_user, :push_code, project) ||
+ (current_user && current_user.already_forked?(project))
end
def tree_edit_branch(project = @project, ref = @ref)
- if allowed_tree_edit?(project, ref)
- if can_push_branch?(project, ref)
- ref
- else
- project.repository.next_patch_branch
- end
+ return unless can_edit_tree?(project, ref)
+
+ if can_push_branch?(project, ref)
+ ref
+ else
+ project = tree_edit_project(project)
+ project.repository.next_patch_branch
+ end
+ end
+
+ def tree_edit_project(project = @project)
+ if can?(current_user, :push_code, project)
+ project
+ elsif current_user && current_user.already_forked?(project)
+ current_user.fork_of(project)
end
end
+ def edit_in_new_fork_notice_now
+ "You're not allowed to make changes to this project directly." +
+ " A fork of this project is being created that you can make changes in, so you can submit a merge request."
+ end
+
+ def edit_in_new_fork_notice
+ "You're not allowed to make changes to this project directly." +
+ " A fork of this project has been created that you can make changes in, so you can submit a merge request."
+ end
+
+ def commit_in_fork_help
+ "A new branch will be created in your fork and a new merge request will be started."
+ end
+
def tree_breadcrumbs(tree, max_links = 2)
if @path.present?
part_path = ""
@@ -79,7 +104,7 @@ module TreeHelper
part_path = File.join(part_path, part) unless part_path.empty?
part_path = part if part_path.empty?
- next unless parts.last(2).include?(part) if parts.count > max_links
+ next if parts.count > max_links && !parts.last(2).include?(part)
yield(part, tree_join(@ref, part_path))
end
end
diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb
index 2e69ce923a2..71d33b445c2 100644
--- a/app/helpers/visibility_level_helper.rb
+++ b/app/helpers/visibility_level_helper.rb
@@ -69,7 +69,6 @@ module VisibilityLevelHelper
def skip_level?(form_model, level)
form_model.is_a?(Project) &&
- form_model.forked? &&
- !Gitlab::VisibilityLevel.allowed_fork_levels(form_model.forked_from_project.visibility_level).include?(level)
+ !form_model.visibility_level_allowed?(level)
end
end
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 9beb0de68f3..3bbdd9cee76 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -17,7 +17,7 @@ class Notify < BaseMailer
subject: subject,
body: body.html_safe,
content_type: 'text/html'
- )
+ )
end
# Splits "gitlab.corp.company.com" up into "gitlab.corp.company.com",
diff --git a/app/models/ability.rb b/app/models/ability.rb
index cd5ae0fb0fd..1b3ee757040 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -132,14 +132,14 @@ class Ability
end
def public_project_rules
- project_guest_rules + [
+ @public_project_rules ||= project_guest_rules + [
:download_code,
:fork_project
]
end
def project_guest_rules
- [
+ @project_guest_rules ||= [
:read_project,
:read_wiki,
:read_issue,
@@ -157,7 +157,7 @@ class Ability
end
def project_report_rules
- project_guest_rules + [
+ @project_report_rules ||= project_guest_rules + [
:create_commit_status,
:read_commit_statuses,
:download_code,
@@ -170,7 +170,7 @@ class Ability
end
def project_dev_rules
- project_report_rules + [
+ @project_dev_rules ||= project_report_rules + [
:admin_merge_request,
:create_merge_request,
:create_wiki,
@@ -181,7 +181,7 @@ class Ability
end
def project_archived_rules
- [
+ @project_archived_rules ||= [
:create_merge_request,
:push_code,
:push_code_to_protected_branches,
@@ -191,7 +191,7 @@ class Ability
end
def project_master_rules
- project_dev_rules + [
+ @project_master_rules ||= project_dev_rules + [
:push_code_to_protected_branches,
:update_project_snippet,
:update_merge_request,
@@ -206,7 +206,7 @@ class Ability
end
def project_admin_rules
- project_master_rules + [
+ @project_admin_rules ||= project_master_rules + [
:change_namespace,
:change_visibility_level,
:rename_project,
@@ -332,7 +332,7 @@ class Ability
end
if snippet.public? || snippet.internal?
- rules << :read_personal_snippet
+ rules << :read_personal_snippet
end
rules
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index faa0bdf840b..be69d317d73 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -2,32 +2,34 @@
#
# Table name: application_settings
#
-# id :integer not null, primary key
-# default_projects_limit :integer
-# signup_enabled :boolean
-# signin_enabled :boolean
-# gravatar_enabled :boolean
-# sign_in_text :text
-# created_at :datetime
-# updated_at :datetime
-# home_page_url :string(255)
-# default_branch_protection :integer default(2)
-# twitter_sharing_enabled :boolean default(TRUE)
-# restricted_visibility_levels :text
-# version_check_enabled :boolean default(TRUE)
-# max_attachment_size :integer default(10), not null
-# default_project_visibility :integer
-# default_snippet_visibility :integer
-# restricted_signup_domains :text
-# user_oauth_applications :boolean default(TRUE)
-# after_sign_out_path :string(255)
-# session_expire_delay :integer default(10080), not null
-# import_sources :text
-# help_page_text :text
-# admin_notification_email :string(255)
-# shared_runners_enabled :boolean default(TRUE), not null
-# max_artifacts_size :integer default(100), not null
-# runners_registration_token :string(255)
+# id :integer not null, primary key
+# default_projects_limit :integer
+# signup_enabled :boolean
+# signin_enabled :boolean
+# gravatar_enabled :boolean
+# sign_in_text :text
+# created_at :datetime
+# updated_at :datetime
+# home_page_url :string(255)
+# default_branch_protection :integer default(2)
+# twitter_sharing_enabled :boolean default(TRUE)
+# restricted_visibility_levels :text
+# version_check_enabled :boolean default(TRUE)
+# max_attachment_size :integer default(10), not null
+# default_project_visibility :integer
+# default_snippet_visibility :integer
+# restricted_signup_domains :text
+# user_oauth_applications :boolean default(TRUE)
+# after_sign_out_path :string(255)
+# session_expire_delay :integer default(10080), not null
+# import_sources :text
+# help_page_text :text
+# admin_notification_email :string(255)
+# shared_runners_enabled :boolean default(TRUE), not null
+# max_artifacts_size :integer default(100), not null
+# runners_registration_token :string(255)
+# require_two_factor_authentication :boolean default(TRUE)
+# two_factor_grace_period :integer default(48)
#
class ApplicationSetting < ActiveRecord::Base
@@ -42,21 +44,32 @@ class ApplicationSetting < ActiveRecord::Base
attr_accessor :restricted_signup_domains_raw
validates :session_expire_delay,
- presence: true,
- numericality: { only_integer: true, greater_than_or_equal_to: 0 }
+ presence: true,
+ numericality: { only_integer: true, greater_than_or_equal_to: 0 }
validates :home_page_url,
- allow_blank: true,
- url: true,
- if: :home_page_url_column_exist
+ allow_blank: true,
+ url: true,
+ if: :home_page_url_column_exist
validates :after_sign_out_path,
- allow_blank: true,
- url: true
+ allow_blank: true,
+ url: true
validates :admin_notification_email,
- allow_blank: true,
- email: true
+ allow_blank: true,
+ email: true
+
+ validates :two_factor_grace_period,
+ numericality: { greater_than_or_equal_to: 0 }
+
+ validates :recaptcha_site_key,
+ presence: true,
+ if: :recaptcha_enabled
+
+ validates :recaptcha_private_key,
+ presence: true,
+ if: :recaptcha_enabled
validates_each :restricted_visibility_levels do |record, attr, value|
unless value.nil?
@@ -112,6 +125,8 @@ class ApplicationSetting < ActiveRecord::Base
import_sources: ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git'],
shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
max_artifacts_size: Settings.artifacts['max_size'],
+ require_two_factor_authentication: false,
+ two_factor_grace_period: 48
)
end
@@ -126,12 +141,16 @@ class ApplicationSetting < ActiveRecord::Base
def restricted_signup_domains_raw=(values)
self.restricted_signup_domains = []
self.restricted_signup_domains = values.split(
- /\s*[,;]\s* # comma or semicolon, optionally surrounded by whitespace
- | # or
- \s # any whitespace character
- | # or
- [\r\n] # any number of newline characters
- /x)
+ /\s*[,;]\s* # comma or semicolon, optionally surrounded by whitespace
+ | # or
+ \s # any whitespace character
+ | # or
+ [\r\n] # any number of newline characters
+ /x)
self.restricted_signup_domains.reject! { |d| d.empty? }
end
+
+ def runners_registration_token
+ ensure_runners_registration_token!
+ end
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 6d9cdb95295..3e67b2771c1 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -135,6 +135,16 @@ module Ci
predefined_variables + yaml_variables + project_variables + trigger_variables
end
+ def merge_request
+ merge_requests = MergeRequest.includes(:merge_request_diff)
+ .where(source_branch: ref, source_project_id: commit.gl_project_id)
+ .reorder(iid: :asc)
+
+ merge_requests.find do |merge_request|
+ merge_request.commits.any? { |ci| ci.id == commit.sha }
+ end
+ end
+
def project
commit.project
end
@@ -170,7 +180,8 @@ module Ci
def extract_coverage(text, regex)
begin
- matches = text.gsub(Regexp.new(regex)).to_a.last
+ matches = text.scan(Regexp.new(regex)).last
+ matches = matches.last if matches.kind_of?(Array)
coverage = matches.gsub(/\d+(\.\d+)?/).first
if coverage.present?
@@ -183,8 +194,11 @@ module Ci
end
def raw_trace
- if File.exist?(path_to_trace)
+ if File.file?(path_to_trace)
File.read(path_to_trace)
+ elsif project.ci_id && File.file?(old_path_to_trace)
+ # Temporary fix for build trace data integrity
+ File.read(old_path_to_trace)
else
# backward compatibility
read_attribute :trace
@@ -201,8 +215,8 @@ module Ci
end
def trace=(trace)
- unless Dir.exists? dir_to_trace
- FileUtils.mkdir_p dir_to_trace
+ unless Dir.exists?(dir_to_trace)
+ FileUtils.mkdir_p(dir_to_trace)
end
File.write(path_to_trace, trace)
@@ -220,6 +234,55 @@ module Ci
"#{dir_to_trace}/#{id}.log"
end
+ ##
+ # Deprecated
+ #
+ # This is a hotfix for CI build data integrity, see #4246
+ # Should be removed in 8.4, after CI files migration has been done.
+ #
+ def old_dir_to_trace
+ File.join(
+ Settings.gitlab_ci.builds_path,
+ created_at.utc.strftime("%Y_%m"),
+ project.ci_id.to_s
+ )
+ end
+
+ ##
+ # Deprecated
+ #
+ # This is a hotfix for CI build data integrity, see #4246
+ # Should be removed in 8.4, after CI files migration has been done.
+ #
+ def old_path_to_trace
+ "#{old_dir_to_trace}/#{id}.log"
+ end
+
+ ##
+ # Deprecated
+ #
+ # This contains a hotfix for CI build data integrity, see #4246
+ #
+ # This method is used by `ArtifactUploader` to create a store_dir.
+ # Warning: Uploader uses it after AND before file has been stored.
+ #
+ # This method returns old path to artifacts only if it already exists.
+ #
+ def artifacts_path
+ old = File.join(created_at.utc.strftime('%Y_%m'),
+ project.ci_id.to_s,
+ id.to_s)
+
+ old_store = File.join(ArtifactUploader.artifacts_path, old)
+ return old if project.ci_id && File.directory?(old_store)
+
+ File.join(
+ created_at.utc.strftime('%Y_%m'),
+ project.id.to_s,
+ id.to_s
+ )
+ end
+
def token
project.runners_token
end
diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb
index 6bf596e5d3e..d2a29236942 100644
--- a/app/models/ci/commit.rb
+++ b/app/models/ci/commit.rb
@@ -218,16 +218,6 @@ module Ci
update!(committed_at: DateTime.now)
end
- ##
- # This method checks if build status should be displayed.
- #
- # Build status should be available only if builds are enabled
- # on project level and `.gitlab-ci.yml` file is present.
- #
- def show_build_status?
- project.builds_enabled? && ci_yaml_file
- end
-
private
def save_yaml_error(error)
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index f56fd3e02d4..18a00f95b48 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -95,14 +95,12 @@ module Issuable
opened? || reopened?
end
- # Deprecated. Still exists to preserve API compatibility.
def downvotes
- 0
+ notes.awards.where(note: "thumbsdown").count
end
- # Deprecated. Still exists to preserve API compatibility.
def upvotes
- 0
+ notes.awards.where(note: "thumbsup").count
end
def subscribed?(user)
@@ -161,6 +159,14 @@ module Issuable
self.class.to_s.underscore
end
+ # Returns a Hash of attributes to be used for Twitter card metadata
+ def card_attributes
+ {
+ 'Author' => author.try(:name),
+ 'Assignee' => assignee.try(:name)
+ }
+ end
+
def notes_with_associations
notes.includes(:author, :project)
end
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index d4e3099453d..6316ee208b5 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -44,14 +44,14 @@ module Mentionable
end
def all_references(current_user = self.author, text = nil)
- ext = Gitlab::ReferenceExtractor.new(self.project, current_user)
+ ext = Gitlab::ReferenceExtractor.new(self.project, current_user, self.author)
if text
ext.analyze(text)
else
self.class.mentionable_attrs.each do |attr, options|
text = send(attr)
- options[:cache_key] = [self, attr] if options.delete(:cache)
+ options[:cache_key] = [self, attr] if options.delete(:cache) && self.persisted?
ext.analyze(text, options)
end
end
diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb
index 808d80b0530..fc6f83b918b 100644
--- a/app/models/concerns/participable.rb
+++ b/app/models/concerns/participable.rb
@@ -37,7 +37,7 @@ module Participable
# Be aware that this method makes a lot of sql queries.
# Save result into variable if you are going to reuse it inside same request
- def participants(current_user = self.author, load_lazy_references: true)
+ def participants(current_user = self.author)
participants =
Gitlab::ReferenceExtractor.lazily do
self.class.participant_attrs.flat_map do |attr|
diff --git a/app/models/concerns/token_authenticatable.rb b/app/models/concerns/token_authenticatable.rb
index 56d38fe8250..885deaf78d2 100644
--- a/app/models/concerns/token_authenticatable.rb
+++ b/app/models/concerns/token_authenticatable.rb
@@ -13,20 +13,21 @@ module TokenAuthenticatable
@token_fields << token_field
define_singleton_method("find_by_#{token_field}") do |token|
- where(token_field => token).first if token
+ find_by(token_field => token) if token
end
define_method("ensure_#{token_field}") do
current_token = read_attribute(token_field)
- if current_token.blank?
- write_attribute(token_field, generate_token_for(token_field))
- else
- current_token
- end
+ current_token.blank? ? write_new_token(token_field) : current_token
+ end
+
+ define_method("ensure_#{token_field}!") do
+ send("reset_#{token_field}!") if read_attribute(token_field).blank?
+ read_attribute(token_field)
end
define_method("reset_#{token_field}!") do
- write_attribute(token_field, generate_token_for(token_field))
+ write_new_token(token_field)
save!
end
end
@@ -34,10 +35,15 @@ module TokenAuthenticatable
private
- def generate_token_for(token_field)
+ def write_new_token(token_field)
+ new_token = generate_token(token_field)
+ write_attribute(token_field, new_token)
+ end
+
+ def generate_token(token_field)
loop do
token = Devise.friendly_token
- break token unless self.class.unscoped.where(token_field => token).first
+ break token unless self.class.unscoped.find_by(token_field => token)
end
end
end
diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb
index 8bfc79d88f8..af1d7562ebe 100644
--- a/app/models/global_milestone.rb
+++ b/app/models/global_milestone.rb
@@ -16,7 +16,7 @@ class GlobalMilestone
end
def safe_title
- @title.to_slug.to_s
+ @title.to_slug.normalize.to_s
end
def expired?
diff --git a/app/models/identity.rb b/app/models/identity.rb
index ad60154be71..8bcdc194953 100644
--- a/app/models/identity.rb
+++ b/app/models/identity.rb
@@ -12,6 +12,7 @@
class Identity < ActiveRecord::Base
include Sortable
+ include CaseSensitivity
belongs_to :user
validates :provider, presence: true
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 4571d7f0ee1..80ecd15077f 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -86,7 +86,7 @@ class Issue < ActiveRecord::Base
def referenced_merge_requests
Gitlab::ReferenceExtractor.lazily do
[self, *notes].flat_map do |note|
- note.all_references(load_lazy_references: false).merge_requests
+ note.all_references.merge_requests
end
end.sort_by(&:iid)
end
diff --git a/app/models/jira_issue.rb b/app/models/jira_issue.rb
new file mode 100644
index 00000000000..5b21aac5e43
--- /dev/null
+++ b/app/models/jira_issue.rb
@@ -0,0 +1,2 @@
+class JiraIssue < ExternalIssue
+end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index f6f77a16267..ac25d38eb63 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -194,9 +194,7 @@ class MergeRequest < ActiveRecord::Base
similar_mrs = similar_mrs.where('id not in (?)', self.id) if self.id
if similar_mrs.any?
errors.add :validate_branches,
- "Cannot Create: This merge request already exists: #{
- similar_mrs.pluck(:title)
- }"
+ "Cannot Create: This merge request already exists: #{similar_mrs.pluck(:title)}"
end
end
end
@@ -337,7 +335,7 @@ class MergeRequest < ActiveRecord::Base
issues = commits.flat_map { |c| c.closes_issues(current_user) }
issues.push(*Gitlab::ClosingIssueExtractor.new(project, current_user).
closed_by_message(description))
- issues.uniq
+ issues.uniq(&:id)
else
[]
end
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 1c4e101cc10..adafabbec07 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -45,7 +45,7 @@ class Namespace < ActiveRecord::Base
class << self
def by_path(path)
- where('lower(path) = :value', value: path.downcase).first
+ find_by('lower(path) = :value', value: path.downcase)
end
# Case insensetive search for namespace by path or name
@@ -148,6 +148,6 @@ class Namespace < ActiveRecord::Base
end
def find_fork_of(project)
- projects.joins(:forked_project_link).where('forked_project_links.forked_from_project_id = ?', project.id).first
+ projects.joins(:forked_project_link).find_by('forked_project_links.forked_from_project_id = ?', project.id)
end
end
diff --git a/app/models/note.rb b/app/models/note.rb
index 8c5b5836f9a..3d5b663c99f 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -107,9 +107,16 @@ class Note < ActiveRecord::Base
end
def grouped_awards
+ notes = {}
+
awards.select(:note).distinct.map do |note|
- [ note.note, where(note: note.note) ]
+ notes[note.note] = where(note: note.note)
end
+
+ notes["thumbsup"] ||= Note.none
+ notes["thumbsdown"] ||= Note.none
+
+ notes
end
end
@@ -339,14 +346,12 @@ class Note < ActiveRecord::Base
read_attribute(:system)
end
- # Deprecated. Still exists to preserve API compatibility.
def downvote?
- false
+ is_award && note == "thumbsdown"
end
- # Deprecated. Still exists to preserve API compatibility.
def upvote?
- false
+ is_award && note == "thumbsup"
end
def editable?
diff --git a/app/models/project.rb b/app/models/project.rb
index e1f7bf971e3..017471995ec 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -64,6 +64,19 @@ class Project < ActiveRecord::Base
update_column(:last_activity_at, self.created_at)
end
+ # update visibility_levet of forks
+ after_update :update_forks_visibility_level
+ def update_forks_visibility_level
+ return unless visibility_level < visibility_level_was
+
+ forks.each do |forked_project|
+ if forked_project.visibility_level > visibility_level
+ forked_project.visibility_level = visibility_level
+ forked_project.save!
+ end
+ end
+ end
+
ActsAsTaggableOn.strict_case_match = true
acts_as_taggable_on :tags
@@ -100,9 +113,12 @@ class Project < ActiveRecord::Base
has_one :gitlab_issue_tracker_service, dependent: :destroy
has_one :external_wiki_service, dependent: :destroy
- has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id"
+ has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id"
+ has_one :forked_from_project, through: :forked_project_link
+
+ has_many :forked_project_links, foreign_key: "forked_from_project_id"
+ has_many :forks, through: :forked_project_links, source: :forked_to_project
- has_one :forked_from_project, through: :forked_project_link
# Merge Requests for target project should be removed with it
has_many :merge_requests, dependent: :destroy, foreign_key: 'target_project_id'
# Merge requests from source project should be kept when source project was removed
@@ -265,7 +281,7 @@ class Project < ActiveRecord::Base
joins(:namespace).
iwhere('namespaces.path' => namespace_path)
- projects.where('projects.path' => project_path).take ||
+ projects.find_by('projects.path' => project_path) ||
projects.iwhere('projects.path' => project_path).take
end
@@ -450,7 +466,7 @@ class Project < ActiveRecord::Base
end
def external_issue_tracker
- @external_issues_tracker ||= external_issues_trackers.select(&:activated?).first
+ @external_issues_tracker ||= external_issues_trackers.find(&:activated?)
end
def can_have_issues_tracker_id?
@@ -496,7 +512,11 @@ class Project < ActiveRecord::Base
end
def ci_service
- @ci_service ||= ci_services.select(&:activated?).first
+ @ci_service ||= ci_services.find(&:activated?)
+ end
+
+ def jira_tracker?
+ issues_tracker.to_param == 'jira'
end
def avatar_type
@@ -535,7 +555,9 @@ class Project < ActiveRecord::Base
end
def send_move_instructions(old_path_with_namespace)
- NotificationService.new.project_was_moved(self, old_path_with_namespace)
+ # New project path needs to be committed to the DB or notification will
+ # retrieve stale information
+ run_after_commit { NotificationService.new.project_was_moved(self, old_path_with_namespace) }
end
def owner
@@ -547,7 +569,7 @@ class Project < ActiveRecord::Base
end
def project_member_by_name_or_email(name = nil, email = nil)
- user = users.where('name like ? or email like ?', name, email).first
+ user = users.find_by('name like ? or email like ?', name, email)
project_members.where(user: user) if user
end
@@ -722,7 +744,7 @@ class Project < ActiveRecord::Base
end
def project_member(user)
- project_members.where(user_id: user).first
+ project_members.find_by(user_id: user)
end
def default_branch
@@ -764,7 +786,7 @@ class Project < ActiveRecord::Base
end
def forks_count
- ForkedProjectLink.where(forked_from_project_id: self.id).count
+ forks.count
end
def find_label(name)
@@ -799,6 +821,10 @@ class Project < ActiveRecord::Base
false
end
+ def jira_tracker_active?
+ jira_tracker? && jira_service.active
+ end
+
def ci_commit(sha)
ci_commits.find_by(sha: sha)
end
@@ -850,4 +876,13 @@ class Project < ActiveRecord::Base
def build_timeout_in_minutes=(value)
self.build_timeout = value.to_i * 60
end
+
+ def open_issues_count
+ issues.opened.count
+ end
+
+ def visibility_level_allowed?(level)
+ return true unless forked?
+ Gitlab::VisibilityLevel.allowed_fork_levels(forked_from_project.visibility_level).include?(level.to_i)
+ end
end
diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb
index 0a61ad96a0e..aa8746beb80 100644
--- a/app/models/project_services/bamboo_service.rb
+++ b/app/models/project_services/bamboo_service.rb
@@ -27,12 +27,10 @@ class BambooService < CiService
validates :build_key, presence: true, if: :activated?
validates :username,
presence: true,
- if: ->(service) { service.password? },
- if: :activated?
+ if: ->(service) { service.activated? && service.password }
validates :password,
presence: true,
- if: ->(service) { service.username? },
- if: :activated?
+ if: ->(service) { service.activated? && service.username }
attr_accessor :response
diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb
index 27fc19379f1..15c7c907f7e 100644
--- a/app/models/project_services/flowdock_service.rb
+++ b/app/models/project_services/flowdock_service.rb
@@ -58,6 +58,6 @@ class FlowdockService < Service
repo_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}",
commit_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/commit/%s",
diff_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/compare/%s...%s",
- )
+ )
end
end
diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb
index 91ef267ad79..202fee042e3 100644
--- a/app/models/project_services/gemnasium_service.rb
+++ b/app/models/project_services/gemnasium_service.rb
@@ -57,6 +57,6 @@ class GemnasiumService < Service
token: token,
api_key: api_key,
repo: project.repository.path_to_repo
- )
+ )
end
end
diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb
index d73182d40ac..b64d97ce75d 100644
--- a/app/models/project_services/gitlab_ci_service.rb
+++ b/app/models/project_services/gitlab_ci_service.rb
@@ -18,6 +18,11 @@
# note_events :boolean default(TRUE), not null
#
+# TODO(ayufan): The GitLabCiService is deprecated and the type should be removed when the database entries are removed
class GitlabCiService < CiService
- # this is no longer used
+ # We override the active accessor to always make GitLabCiService disabled
+ # Otherwise the GitLabCiService can be picked, but should never be since it's deprecated
+ def active
+ false
+ end
end
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 35e30b1cb0b..e216f406e1c 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -19,9 +19,24 @@
#
class JiraService < IssueTrackerService
+ include HTTParty
include Gitlab::Application.routes.url_helpers
- prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
+ DEFAULT_API_VERSION = 2
+
+ prop_accessor :username, :password, :api_url, :jira_issue_transition_id,
+ :title, :description, :project_url, :issues_url, :new_issue_url
+
+ before_validation :set_api_url, :set_jira_issue_transition_id
+
+ before_update :reset_password
+
+ def reset_password
+ # don't reset the password if a new one is provided
+ if api_url_changed? && !password_touched?
+ self.password = nil
+ end
+ end
def help
line1 = 'Setting `project_url`, `issues_url` and `new_issue_url` will '\
@@ -54,4 +69,228 @@ class JiraService < IssueTrackerService
def to_param
'jira'
end
+
+ def fields
+ super.push(
+ { type: 'text', name: 'api_url', placeholder: 'https://jira.example.com/rest/api/2' },
+ { type: 'text', name: 'username', placeholder: '' },
+ { type: 'password', name: 'password', placeholder: '' },
+ { type: 'text', name: 'jira_issue_transition_id', placeholder: '2' }
+ )
+ end
+
+ def execute(push, issue = nil)
+ if issue.nil?
+ # No specific issue, that means
+ # we just want to test settings
+ test_settings
+ else
+ close_issue(push, issue)
+ end
+ end
+
+ def create_cross_reference_note(mentioned, noteable, author)
+ issue_name = mentioned.id
+ project = self.project
+ noteable_name = noteable.class.name.underscore.downcase
+ noteable_id = if noteable.is_a?(Commit)
+ noteable.id
+ else
+ noteable.iid
+ end
+
+ entity_url = build_entity_url(noteable_name.to_sym, noteable_id)
+
+ data = {
+ user: {
+ name: author.name,
+ url: resource_url(user_path(author)),
+ },
+ project: {
+ name: project.path_with_namespace,
+ url: resource_url(namespace_project_path(project.namespace, project))
+ },
+ entity: {
+ name: noteable_name.humanize.downcase,
+ url: entity_url
+ }
+ }
+
+ add_comment(data, issue_name)
+ end
+
+ def test_settings
+ result = JiraService.get(
+ jira_api_test_url,
+ headers: {
+ 'Content-Type' => 'application/json',
+ 'Authorization' => "Basic #{auth}"
+ }
+ )
+
+ case result.code
+ when 201, 200
+ Rails.logger.info("#{self.class.name} SUCCESS #{result.code}: Successfully connected to #{api_url}.")
+ true
+ else
+ Rails.logger.info("#{self.class.name} ERROR #{result.code}: #{result.parsed_response}")
+ false
+ end
+ rescue Errno::ECONNREFUSED => e
+ Rails.logger.info "#{self.class.name} ERROR: #{e.message}. API URL: #{api_url}."
+ false
+ end
+
+ private
+
+ def build_api_url_from_project_url
+ server = URI(project_url)
+ 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}"
+ rescue
+ "" # looks like project URL was not valid
+ end
+
+ def set_api_url
+ self.api_url = build_api_url_from_project_url if self.api_url.blank?
+ end
+
+ def set_jira_issue_transition_id
+ self.jira_issue_transition_id ||= "2"
+ end
+
+ def close_issue(entity, issue)
+ commit_id = if entity.is_a?(Commit)
+ entity.id
+ elsif entity.is_a?(MergeRequest)
+ entity.last_commit.id
+ end
+ commit_url = build_entity_url(:commit, commit_id)
+
+ # Depending on the JIRA project's workflow, a comment during transition
+ # may or may not be allowed. Split the operation in to two calls so the
+ # comment always works.
+ transition_issue(issue)
+ add_issue_solved_comment(issue, commit_id, commit_url)
+ end
+
+ def transition_issue(issue)
+ message = {
+ transition: {
+ id: jira_issue_transition_id
+ }
+ }
+ send_message(close_issue_url(issue.iid), message.to_json)
+ end
+
+ def add_issue_solved_comment(issue, commit_id, commit_url)
+ comment = {
+ body: "Issue solved with [#{commit_id}|#{commit_url}]."
+ }
+
+ send_message(comment_url(issue.iid), comment.to_json)
+ end
+
+ def add_comment(data, issue_name)
+ url = comment_url(issue_name)
+ user_name = data[:user][:name]
+ user_url = data[:user][:url]
+ entity_name = data[:entity][:name]
+ entity_url = data[:entity][:url]
+ project_name = data[:project][:name]
+
+ message = {
+ body: "[#{user_name}|#{user_url}] mentioned this issue in [a #{entity_name} of #{project_name}|#{entity_url}]."
+ }
+
+ unless existing_comment?(issue_name, message[:body])
+ send_message(url, message.to_json)
+ end
+ end
+
+
+ def auth
+ require 'base64'
+ Base64.urlsafe_encode64("#{self.username}:#{self.password}")
+ end
+
+ def send_message(url, message)
+ result = JiraService.post(
+ url,
+ body: message,
+ headers: {
+ 'Content-Type' => 'application/json',
+ 'Authorization' => "Basic #{auth}"
+ }
+ )
+
+ message = case result.code
+ when 201, 200, 204
+ "#{self.class.name} SUCCESS #{result.code}: Successfully posted to #{url}."
+ when 401
+ "#{self.class.name} ERROR 401: Unauthorized. Check the #{self.username} credentials and JIRA access permissions and try again."
+ else
+ "#{self.class.name} ERROR #{result.code}: #{result.parsed_response}"
+ end
+
+ Rails.logger.info(message)
+ message
+ rescue URI::InvalidURIError, Errno::ECONNREFUSED => e
+ Rails.logger.info "#{self.class.name} ERROR: #{e.message}. Hostname: #{url}."
+ end
+
+ def existing_comment?(issue_name, new_comment)
+ result = JiraService.get(
+ comment_url(issue_name),
+ headers: {
+ 'Content-Type' => 'application/json',
+ 'Authorization' => "Basic #{auth}"
+ }
+ )
+
+ case result.code
+ when 201, 200
+ existing_comments = JSON.parse(result.body)['comments']
+
+ if existing_comments.present?
+ return existing_comments.map { |comment| comment['body'].include?(new_comment) }.any?
+ end
+ end
+
+ false
+ rescue JSON::ParserError
+ false
+ end
+
+ def resource_url(resource)
+ "#{Settings.gitlab['url'].chomp("/")}#{resource}"
+ end
+
+ def build_entity_url(entity_name, entity_id)
+ resource_url(
+ polymorphic_url(
+ [
+ self.project.namespace.becomes(Namespace),
+ self.project,
+ entity_name
+ ],
+ id: entity_id,
+ routing_type: :path
+ )
+ )
+ end
+
+ def close_issue_url(issue_name)
+ "#{self.api_url}/issue/#{issue_name}/transitions"
+ end
+
+ def comment_url(issue_name)
+ "#{self.api_url}/issue/#{issue_name}/comment"
+ end
+
+ def jira_api_test_url
+ "#{self.api_url}/myself"
+ end
end
diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb
index 29d4236745a..a63700693d7 100644
--- a/app/models/project_services/teamcity_service.rb
+++ b/app/models/project_services/teamcity_service.rb
@@ -27,12 +27,10 @@ class TeamcityService < CiService
validates :build_type, presence: true, if: :activated?
validates :username,
presence: true,
- if: ->(service) { service.password? },
- if: :activated?
+ if: ->(service) { service.activated? && service.password }
validates :password,
presence: true,
- if: ->(service) { service.username? },
- if: :activated?
+ if: ->(service) { service.activated? && service.username }
attr_accessor :response
@@ -147,6 +145,6 @@ class TeamcityService < CiService
'</build>',
headers: { 'Content-type' => 'application/xml' },
basic_auth: auth
- )
+ )
end
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 2c25f4ce451..a9bf4eb4033 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -76,7 +76,9 @@ class Repository
path: path,
limit: limit,
offset: offset,
- follow: path.present?
+ # --follow doesn't play well with --skip. See:
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520
+ follow: false
}
commits = Gitlab::Git::Commit.where(options)
@@ -592,47 +594,54 @@ class Repository
Gitlab::Popen.popen(args, path_to_repo)
end
- def commit_with_hooks(current_user, branch)
- oldrev = Gitlab::Git::BLANK_SHA
- ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
- was_empty = empty?
-
- # Create temporary ref
+ def with_tmp_ref(oldrev = nil)
random_string = SecureRandom.hex
tmp_ref = "refs/tmp/#{random_string}/head"
- unless was_empty
- oldrev = find_branch(branch).target
+ if oldrev && !Gitlab::Git.blank_ref?(oldrev)
rugged.references.create(tmp_ref, oldrev)
end
# Make commit in tmp ref
- newrev = yield(tmp_ref)
+ yield(tmp_ref)
+ ensure
+ rugged.references.delete(tmp_ref) rescue nil
+ end
+
+ def commit_with_hooks(current_user, branch)
+ oldrev = Gitlab::Git::BLANK_SHA
+ ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
+ was_empty = empty?
- unless newrev
- raise CommitError.new('Failed to create commit')
+ unless was_empty
+ oldrev = find_branch(branch).target
end
- GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
- if was_empty
- # Create branch
- rugged.references.create(ref, newrev)
- else
- # Update head
- current_head = find_branch(branch).target
+ with_tmp_ref(oldrev) do |tmp_ref|
+ # Make commit in tmp ref
+ newrev = yield(tmp_ref)
+
+ unless newrev
+ raise CommitError.new('Failed to create commit')
+ end
- # Make sure target branch was not changed during pre-receive hook
- if current_head == oldrev
- rugged.references.update(ref, newrev)
+ GitHooksService.new.execute(current_user, path_to_repo, oldrev, newrev, ref) do
+ if was_empty
+ # Create branch
+ rugged.references.create(ref, newrev)
else
- raise CommitError.new('Commit was rejected because branch received new push')
+ # 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
end
end
end
- rescue GitHooksService::PreReceiveError
- # Remove tmp ref and return error to user
- rugged.references.delete(tmp_ref)
- raise
end
private
diff --git a/app/models/user.rb b/app/models/user.rb
index fdd14f4571d..df87f3b79bd 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -26,6 +26,7 @@
# bio :string(255)
# failed_attempts :integer default(0)
# locked_at :datetime
+# unlock_token :string(255)
# username :string(255)
# can_create_group :boolean default(TRUE), not null
# can_create_team :boolean default(TRUE), not null
@@ -220,9 +221,9 @@ class User < ActiveRecord::Base
def find_for_database_authentication(warden_conditions)
conditions = warden_conditions.dup
if login = conditions.delete(:login)
- where(conditions).where(["lower(username) = :value OR lower(email) = :value", { value: login.downcase }]).first
+ where(conditions).find_by("lower(username) = :value OR lower(email) = :value", value: login.downcase)
else
- where(conditions).first
+ find_by(conditions)
end
end
@@ -285,7 +286,7 @@ class User < ActiveRecord::Base
end
def by_username_or_id(name_or_id)
- where('users.username = ? OR users.id = ?', name_or_id.to_s, name_or_id.to_i).first
+ find_by('users.username = ? OR users.id = ?', name_or_id.to_s, name_or_id.to_i)
end
def build_user(attrs = {})
diff --git a/app/services/base_service.rb b/app/services/base_service.rb
index f00ec7408b6..b48ca67d4d2 100644
--- a/app/services/base_service.rb
+++ b/app/services/base_service.rb
@@ -39,10 +39,7 @@ class BaseService
def deny_visibility_level(model, denied_visibility_level = nil)
denied_visibility_level ||= model.visibility_level
- level_name = 'Unknown'
- Gitlab::VisibilityLevel.options.each do |name, level|
- level_name = name if level == denied_visibility_level
- end
+ level_name = Gitlab::VisibilityLevel.level_name(denied_visibility_level)
model.errors.add(
:visibility_level,
diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb
index de18f3bc556..f139872c728 100644
--- a/app/services/create_branch_service.rb
+++ b/app/services/create_branch_service.rb
@@ -1,10 +1,10 @@
require_relative 'base_service'
class CreateBranchService < BaseService
- def execute(branch_name, ref)
+ def execute(branch_name, ref, source_project: @project)
valid_branch = Gitlab::GitRefValidator.validate(branch_name)
if valid_branch == false
- return error('Branch name invalid')
+ return error('Branch name is invalid')
end
repository = project.repository
@@ -13,7 +13,20 @@ class CreateBranchService < BaseService
return error('Branch already exists')
end
- new_branch = repository.add_branch(current_user, branch_name, ref)
+ 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
if new_branch
push_data = build_push_data(project, current_user, new_branch)
diff --git a/app/services/create_commit_builds_service.rb b/app/services/create_commit_builds_service.rb
index 759c334ebe9..31b407efeb1 100644
--- a/app/services/create_commit_builds_service.rb
+++ b/app/services/create_commit_builds_service.rb
@@ -16,9 +16,23 @@ class CreateCommitBuildsService
return false
end
- tag = Gitlab::Git.tag_ref?(origin_ref)
- commit = project.ensure_ci_commit(sha)
+ commit = project.ci_commit(sha)
+ unless commit
+ commit = project.ci_commits.new(sha: sha)
+
+ # Skip creating ci_commit when no gitlab-ci.yml is found
+ unless commit.ci_yaml_file
+ return false
+ end
+
+ # Create a new ci_commit
+ commit.save!
+ end
+
+ # Skip creating builds for commits that have [ci skip]
unless commit.skip_ci?
+ # Create builds for commit
+ tag = Gitlab::Git.tag_ref?(origin_ref)
commit.update_committed!
commit.create_builds(ref, tag, user)
end
diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb
index 9a67b160940..0326a8823e9 100644
--- a/app/services/files/base_service.rb
+++ b/app/services/files/base_service.rb
@@ -3,8 +3,10 @@ module Files
class ValidationError < StandardError; end
def execute
- @current_branch = params[:current_branch]
+ @source_project = params[:source_project] || @project
+ @source_branch = params[:source_branch]
@target_branch = params[:target_branch]
+
@commit_message = params[:commit_message]
@file_path = params[:file_path]
@file_content = if params[:file_content_encoding] == 'base64'
@@ -16,8 +18,8 @@ module Files
# Validate parameters
validate
- # Create new branch if it different from current_branch
- if @target_branch != @current_branch
+ # Create new branch if it different from source_branch
+ if different_branch?
create_target_branch
end
@@ -26,18 +28,14 @@ module Files
else
error("Something went wrong. Your changes were not committed")
end
- rescue Repository::CommitError, GitHooksService::PreReceiveError, ValidationError => ex
+ rescue Repository::CommitError, Gitlab::Git::Repository::InvalidBlobName, GitHooksService::PreReceiveError, ValidationError => ex
error(ex.message)
end
private
- def current_branch
- @current_branch ||= params[:current_branch]
- end
-
- def target_branch
- @target_branch ||= params[:target_branch]
+ def different_branch?
+ @source_branch != @target_branch || @source_project != @project
end
def raise_error(message)
@@ -52,11 +50,11 @@ module Files
end
unless project.empty_repo?
- unless repository.branch_names.include?(@current_branch)
+ unless @source_project.repository.branch_names.include?(@source_branch)
raise_error("You can only create or edit files when you are on a branch")
end
- if @current_branch != @target_branch
+ 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")
end
@@ -65,10 +63,10 @@ module Files
end
def create_target_branch
- result = CreateBranchService.new(project, current_user).execute(@target_branch, @current_branch)
+ result = CreateBranchService.new(project, current_user).execute(@target_branch, @source_branch, source_project: @source_project)
unless result[:status] == :success
- raise_error("Something went wrong when we tried to create #{@target_branch} for you")
+ raise_error("Something went wrong when we tried to create #{@target_branch} for you: #{result[:message]}")
end
end
end
diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb
index 2348920cc58..e4cde4a2fd8 100644
--- a/app/services/files/create_service.rb
+++ b/app/services/files/create_service.rb
@@ -26,7 +26,7 @@ module Files
unless project.empty_repo?
@file_path.slice!(0) if @file_path.start_with?('/')
- blob = repository.blob_at_branch(@current_branch, @file_path)
+ 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")
diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb
index 3d85f97b7e5..a1a20e47681 100644
--- a/app/services/issues/close_service.rb
+++ b/app/services/issues/close_service.rb
@@ -1,6 +1,11 @@
module Issues
class CloseService < Issues::BaseService
def execute(issue, commit = nil)
+ if project.jira_tracker? && project.jira_service.active
+ project.jira_service.execute(commit, issue)
+ return issue
+ end
+
if project.default_issues_tracker? && issue.close
event_service.close_issue(issue, current_user)
create_note(issue, commit)
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index b26c7513f5b..8b3d56c2b4c 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -112,7 +112,7 @@ module MergeRequests
merge_requests_for_source_branch.each do |merge_request|
SystemNoteService.change_branch_presence(
- merge_request, merge_request.project, @current_user,
+ merge_request, merge_request.project, @current_user,
:source, @branch_name, presence)
end
end
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index 69bdd045ddf..895e089bea3 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -3,12 +3,16 @@ 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
+ if new_visibility
+ if 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
end
+
+ return false unless visibility_level_allowed?(new_visibility)
end
new_branch = params[:default_branch]
@@ -23,5 +27,19 @@ module Projects
end
end
end
+
+ private
+
+ def visibility_level_allowed?(level)
+ return true if project.visibility_level_allowed?(level)
+
+ level_name = Gitlab::VisibilityLevel.level_name(level)
+ project.errors.add(
+ :visibility_level,
+ "#{level_name} could not be set as visibility level of this project - parent project settings are more restrictive"
+ )
+
+ false
+ end
end
end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 6975b2ee55b..98a71cbf1ad 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -241,9 +241,14 @@ class SystemNoteService
note_options.merge!(noteable: noteable)
end
- create_note(note_options)
+ if noteable.is_a?(ExternalIssue)
+ noteable.project.issues_tracker.create_cross_reference_note(noteable, mentioner, author)
+ else
+ create_note(note_options)
+ end
end
+
def self.cross_reference?(note_text)
note_text.start_with?(cross_reference_note_prefix)
end
@@ -259,7 +264,7 @@ class SystemNoteService
#
# Returns Boolean
def self.cross_reference_disallowed?(noteable, mentioner)
- return true if noteable.is_a?(ExternalIssue)
+ return true if noteable.is_a?(ExternalIssue) && !noteable.project.jira_tracker_active?
return false unless mentioner.is_a?(MergeRequest)
return false unless noteable.is_a?(Commit)
diff --git a/app/uploaders/artifact_uploader.rb b/app/uploaders/artifact_uploader.rb
index 1dccc39e7e2..1b0ae6c0056 100644
--- a/app/uploaders/artifact_uploader.rb
+++ b/app/uploaders/artifact_uploader.rb
@@ -20,16 +20,12 @@ class ArtifactUploader < CarrierWave::Uploader::Base
@build, @field = build, field
end
- def artifacts_path
- File.join(build.created_at.utc.strftime('%Y_%m'), build.project.id.to_s, build.id.to_s)
- end
-
def store_dir
- File.join(ArtifactUploader.artifacts_path, artifacts_path)
+ File.join(self.class.artifacts_path, @build.artifacts_path)
end
def cache_dir
- File.join(ArtifactUploader.artifacts_cache_path, artifacts_path)
+ File.join(self.class.artifacts_cache_path, @build.artifacts_path)
end
def file_storage?
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 6c355366948..214e0209bb7 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -105,6 +105,18 @@
= f.check_box :signin_enabled
Sign-in enabled
.form-group
+ = f.label :two_factor_authentication, 'Two-Factor authentication', class: 'control-label col-sm-2'
+ .col-sm-10
+ .checkbox
+ = f.label :require_two_factor_authentication do
+ = f.check_box :require_two_factor_authentication
+ Require all users to setup Two-Factor authentication
+ .form-group
+ = f.label :two_factor_authentication, 'Two-Factor grace period (hours)', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.number_field :two_factor_grace_period, min: 0, class: 'form-control', placeholder: '0'
+ .help-block Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication
+ .form-group
= f.label :restricted_signup_domains, 'Restricted domains for sign-ups', class: 'control-label col-sm-2'
.col-sm-10
= f.text_area :restricted_signup_domains_raw, placeholder: 'domain.com', class: 'form-control'
@@ -144,5 +156,82 @@
.col-sm-10
= f.number_field :max_artifacts_size, class: 'form-control'
+ %fieldset
+ %legend Metrics
+ %p
+ These settings require a restart to take effect.
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :metrics_enabled do
+ = f.check_box :metrics_enabled
+ Enable InfluxDB Metrics
+ .form-group
+ = f.label :metrics_host, 'InfluxDB host', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.text_field :metrics_host, class: 'form-control', placeholder: 'influxdb.example.com'
+ .form-group
+ = f.label :metrics_port, 'InfluxDB port', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.text_field :metrics_port, class: 'form-control', placeholder: '8089'
+ .help-block
+ The UDP port to use for connecting to InfluxDB. InfluxDB requires that
+ your server configuration specifies a database to store data in when
+ sending messages to this port, without it metrics data will not be
+ saved.
+ .form-group
+ = f.label :metrics_username, 'InfluxDB username', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.text_field :metrics_username, class: 'form-control'
+ .form-group
+ = f.label :metrics_password, 'InfluxDB password', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.text_field :metrics_password, class: 'form-control'
+ .form-group
+ = f.label :metrics_pool_size, 'Connection pool size', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.number_field :metrics_pool_size, class: 'form-control'
+ .help-block
+ The amount of InfluxDB connections to open. Connections are opened
+ lazily. Users using multi-threaded application servers should ensure
+ enough connections are available (at minimum the amount of application
+ server threads).
+ .form-group
+ = f.label :metrics_timeout, 'Connection timeout', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.number_field :metrics_timeout, class: 'form-control'
+ .help-block
+ The amount of seconds after which an InfluxDB connection will time
+ out.
+ .form-group
+ = f.label :metrics_method_call_threshold, 'Method Call Threshold (ms)', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.number_field :metrics_method_call_threshold, class: 'form-control'
+ .help-block
+ A method call is only tracked when it takes longer to complete than
+ the given amount of milliseconds.
+
+ %fieldset
+ %legend Spam and Anti-bot Protection
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :recaptcha_enabled do
+ = f.check_box :recaptcha_enabled
+ Enable reCAPTCHA
+ %span.help-block#recaptcha_help_block Helps preventing bots from creating accounts
+
+ .form-group
+ = f.label :recaptcha_site_key, 'reCAPTCHA Site Key', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.text_field :recaptcha_site_key, class: 'form-control'
+ .help-block
+ Generate site and private keys here:
+ %a{ href: 'http://www.google.com/recaptcha', target: 'blank'} http://www.google.com/recaptcha
+ .form-group
+ = f.label :recaptcha_private_key, 'reCAPTCHA Private Key', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.text_field :recaptcha_private_key, class: 'form-control'
+
.form-actions
= f.submit 'Save', class: 'btn btn-primary'
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 8657d2c71fe..531247e9148 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -80,6 +80,10 @@
%span.pull-right
= API::API::version
%p
+ Git
+ %span.pull-right
+ = Gitlab::Git.version
+ %p
Ruby
%span.pull-right
#{RUBY_VERSION}p#{RUBY_PATCHLEVEL}
diff --git a/app/views/admin/identities/index.html.haml b/app/views/admin/identities/index.html.haml
index 8358a14445b..741d111fb7d 100644
--- a/app/views/admin/identities/index.html.haml
+++ b/app/views/admin/identities/index.html.haml
@@ -1,6 +1,7 @@
- page_title "Identities", @user.name, "Users"
= render 'admin/users/head'
+= link_to 'New Identity', new_admin_user_identity_path, class: 'pull-right btn btn-new'
- if @identities.present?
.table-holder
%table.table
diff --git a/app/views/admin/identities/new.html.haml b/app/views/admin/identities/new.html.haml
new file mode 100644
index 00000000000..e30bf0ef0ee
--- /dev/null
+++ b/app/views/admin/identities/new.html.haml
@@ -0,0 +1,4 @@
+- page_title "New Identity"
+%h3.page-title New identity
+%hr
+= render 'form'
diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml
index c5fb3c95506..c407972cd08 100644
--- a/app/views/admin/runners/index.html.haml
+++ b/app/views/admin/runners/index.html.haml
@@ -3,7 +3,7 @@
To register a new runner you should enter the following registration token.
With this token the runner will request a unique runner token and use that for future communication.
Registration token is
- %code{ id: 'runners-token' } #{current_application_settings.ensure_runners_registration_token}
+ %code{ id: 'runners-token' } #{current_application_settings.runners_registration_token}
.bs-callout.clearfix
.pull-left
diff --git a/app/views/ci/lints/_create.html.haml b/app/views/ci/lints/_create.html.haml
index 77f78caa8d8..f7875e68b7e 100644
--- a/app/views/ci/lints/_create.html.haml
+++ b/app/views/ci/lints/_create.html.haml
@@ -41,5 +41,3 @@
%i.fa.fa-remove.incorrect-syntax
%b Error:
= @error
-
-
diff --git a/app/views/ci/lints/create.js.haml b/app/views/ci/lints/create.js.haml
deleted file mode 100644
index a96c0b11b6e..00000000000
--- a/app/views/ci/lints/create.js.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-:plain
- $(".results").html("#{escape_javascript(render "create")}") \ No newline at end of file
diff --git a/app/views/ci/lints/show.html.haml b/app/views/ci/lints/show.html.haml
index fb9057e4882..a144c43be47 100644
--- a/app/views/ci/lints/show.html.haml
+++ b/app/views/ci/lints/show.html.haml
@@ -1,27 +1,17 @@
%h2 Check your .gitlab-ci.yml
%hr
-= form_tag ci_lint_path, method: :post, remote: true do
- .control-group
- = label_tag :content, "Content of .gitlab-ci.yml", class: 'control-label'
- .controls
- = text_area_tag :content, nil, class: 'form-control span1', rows: 7, require: true
+.row
+ = form_tag ci_lint_path, method: :post do
+ .form-group
+ = label_tag :content, 'Content of .gitlab-ci.yml', class: 'control-label text-nowrap'
+ .col-sm-12
+ = text_area_tag :content, nil, class: 'form-control span1', rows: 7, require: true
+ .col-sm-12
+ .pull-left.prepend-top-10
+ = submit_tag 'Validate', class: 'btn btn-success submit-yml'
- .control-group.clearfix
- .controls.pull-left.prepend-top-10
- = submit_tag "Validate", class: 'btn btn-success submit-yml'
-
-
-%p.text-center.loading
- %i.fa.fa-refresh.fa-spin
-
-.results.prepend-top-20
-
-:javascript
- $(".loading").hide();
- $('form').bind('ajax:beforeSend', function() {
- $(".loading").show();
- });
- $('form').bind('ajax:complete', function() {
- $(".loading").hide();
- });
+.row.prepend-top-20
+ .col-sm-12
+ .results
+ = render partial: 'create' if defined?(@status)
diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml
index 2e77afb7525..f4a3e3162bf 100644
--- a/app/views/dashboard/_projects_head.html.haml
+++ b/app/views/dashboard/_projects_head.html.haml
@@ -1,13 +1,20 @@
= content_for :flash_message do
= render 'shared/project_limit'
+.top-area
+ %ul.left-top-menu
+ = nav_link(page: [dashboard_projects_path, root_path]) do
+ = link_to dashboard_projects_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do
+ Your Projects
+ = nav_link(page: starred_dashboard_projects_path) do
+ = link_to starred_dashboard_projects_path, title: 'Starred Projects', data: {placement: 'right'} do
+ Starred Projects
+ = nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path], html_options: { class: 'hidden-xs' }) do
+ = link_to explore_root_path, title: 'Explore', data: {placement: 'right'} do
+ Explore Projects
-%ul.center-top-menu
- = nav_link(page: [dashboard_projects_path, root_path]) do
- = link_to dashboard_projects_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do
- Your Projects
- = nav_link(page: starred_dashboard_projects_path) do
- = link_to starred_dashboard_projects_path, title: 'Starred Projects', data: {placement: 'right'} do
- Starred Projects
- = nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path], html_options: { class: 'hidden-xs' }) do
- = link_to explore_root_path, title: 'Explore', data: {placement: 'right'} do
- Explore Projects
+ .projects-search-form
+ = search_field_tag :filter_projects, nil, placeholder: 'Filter by name...', class: 'projects-list-filter form-control hidden-xs', spellcheck: false
+ - if current_user.can_create_project?
+ = link_to new_project_path, class: 'btn btn-green' do
+ %i.fa.fa-plus
+ New Project
diff --git a/app/views/dashboard/milestones/show.html.haml b/app/views/dashboard/milestones/show.html.haml
index 44b7efe5232..4316c358dcb 100644
--- a/app/views/dashboard/milestones/show.html.haml
+++ b/app/views/dashboard/milestones/show.html.haml
@@ -1,18 +1,18 @@
- page_title @milestone.title, "Milestones"
- header_title "Milestones", dashboard_milestones_path
-.issuable-details
- .page-title
- .issue-box{ class: "issue-box-#{@milestone.closed? ? 'closed' : 'open'}" }
- - if @milestone.closed?
- Closed
- - else
- Open
+.detail-page-header
+ .status-box{ class: "status-box-#{@milestone.closed? ? 'closed' : 'open'}" }
+ - if @milestone.closed?
+ Closed
+ - else
+ Open
+ %span.identifier
Milestone #{@milestone.title}
- .gray-content-block.middle-block
- %h2.issue-title
- = markdown escape_once(@milestone.title), pipeline: :single_line
+.detail-page-description.gray-content-block.second-block
+ %h2.title
+ = markdown escape_once(@milestone.title), pipeline: :single_line
- if @milestone.complete? && @milestone.active?
.alert.alert-success.prepend-top-default
diff --git a/app/views/dashboard/projects/_projects.html.haml b/app/views/dashboard/projects/_projects.html.haml
index 81a5909e2d2..cea9ffcc748 100644
--- a/app/views/dashboard/projects/_projects.html.haml
+++ b/app/views/dashboard/projects/_projects.html.haml
@@ -1,11 +1,3 @@
.projects-list-holder
- .projects-search-form
- .input-group
- = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false
- - if current_user.can_create_project?
- %span.input-group-btn
- = link_to new_project_path, class: 'btn btn-green' do
- %i.fa.fa-plus
- New Project
= render 'shared/projects/list', projects: @projects, ci: true
diff --git a/app/views/devise/mailer/unlock_instructions.html.erb b/app/views/devise/mailer/unlock_instructions.html.erb
deleted file mode 100644
index 79d6c761d8f..00000000000
--- a/app/views/devise/mailer/unlock_instructions.html.erb
+++ /dev/null
@@ -1,7 +0,0 @@
-<p>Hello <%= @resource.email %>!</p>
-
-<p>Your account has been locked due to an excessive amount of unsuccessful sign in attempts.</p>
-
-<p>Click the link below to unlock your account:</p>
-
-<p><%= link_to 'Unlock your account', unlock_url(@resource, unlock_token: @token) %></p>
diff --git a/app/views/devise/mailer/unlock_instructions.html.haml b/app/views/devise/mailer/unlock_instructions.html.haml
new file mode 100644
index 00000000000..52b327e20c5
--- /dev/null
+++ b/app/views/devise/mailer/unlock_instructions.html.haml
@@ -0,0 +1,10 @@
+%p
+Hello #{@resource.name}!
+
+%p
+ Your GitLab account has been locked due to an excessive amount of unsuccessful
+ sign in attempts. Your account will automatically unlock in
+ = time_ago_in_words(Devise.unlock_in.from_now)
+ or you may click the link below to unlock now.
+
+%p= link_to 'Unlock your account', unlock_url(@resource, unlock_token: @token)
diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml
index 9dc6aeffd59..cb93ff2465e 100644
--- a/app/views/devise/shared/_signup_box.html.haml
+++ b/app/views/devise/shared/_signup_box.html.haml
@@ -6,17 +6,21 @@
.login-heading
%h3 Create an account
.login-body
+ - user = params[:user].present? ? params[:user] : {}
= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|
.devise-errors
= devise_error_messages!
%div
- = f.text_field :name, class: "form-control top", placeholder: "Name", required: true
+ = f.text_field :name, class: "form-control top", value: user[:name], placeholder: "Name", required: true
%div
- = f.text_field :username, class: "form-control middle", placeholder: "Username", required: true
+ = f.text_field :username, class: "form-control middle", value: user[:username], placeholder: "Username", required: true
%div
- = f.email_field :email, class: "form-control middle", placeholder: "Email", required: true
+ = f.email_field :email, class: "form-control middle", value: user[:email], placeholder: "Email", required: true
.form-group.append-bottom-20#password-strength
- = f.password_field :password, class: "form-control bottom", id: "user_password_sign_up", placeholder: "Password", required: true
+ = f.password_field :password, class: "form-control bottom", value: user[:password], id: "user_password_sign_up", placeholder: "Password", required: true
+ %div
+ - if current_application_settings.recaptcha_enabled
+ = recaptcha_tags
%div
= f.submit "Sign up", class: "btn-create btn"
diff --git a/app/views/devise/unlocks/new.html.erb b/app/views/devise/unlocks/new.html.erb
deleted file mode 100644
index f9277d1673f..00000000000
--- a/app/views/devise/unlocks/new.html.erb
+++ /dev/null
@@ -1,12 +0,0 @@
-<h2>Resend unlock instructions</h2>
-
-<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %>
- <%= devise_error_messages! %>
-
- <div><%= f.label :email %><br />
- <%= f.email_field :email %></div>
-
- <div><%= f.submit "Resend unlock instructions" %></div>
-<% end %>
-
-<%= render partial: "devise/shared/links" %>
diff --git a/app/views/devise/unlocks/new.html.haml b/app/views/devise/unlocks/new.html.haml
new file mode 100644
index 00000000000..49c087c0646
--- /dev/null
+++ b/app/views/devise/unlocks/new.html.haml
@@ -0,0 +1,14 @@
+.login-box
+ .login-heading
+ %h3 Resend unlock email
+ .login-body
+ = form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f|
+ .devise-errors
+ = devise_error_messages!
+ .clearfix.append-bottom-20
+ = f.email_field :email, class: 'form-control', placeholder: 'Email', autofocus: 'autofocus', autocapitalize: 'off', autocorrect: 'off'
+ .clearfix
+ = f.submit 'Resend unlock instructions', class: 'btn btn-success'
+
+.clearfix.prepend-top-20
+ = render 'devise/shared/sign_in_link'
diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml
index 76bdd68fd76..b9a958fbe7b 100644
--- a/app/views/explore/projects/index.html.haml
+++ b/app/views/explore/projects/index.html.haml
@@ -6,7 +6,7 @@
- else
= render 'explore/head'
-.gray-content-block.clearfix
+.gray-content-block.clearfix.second-block
= render 'filter'
= render 'projects', projects: @projects
= paginate @projects, theme: "gitlab"
diff --git a/app/views/explore/projects/starred.html.haml b/app/views/explore/projects/starred.html.haml
index e30c3633223..95d46e331f8 100644
--- a/app/views/explore/projects/starred.html.haml
+++ b/app/views/explore/projects/starred.html.haml
@@ -7,7 +7,7 @@
= render 'explore/head'
.explore-trending-block
- .gray-content-block
+ .gray-content-block.second-block
.pull-right
= render 'explore/projects/dropdown'
.oneline
diff --git a/app/views/explore/projects/trending.html.haml b/app/views/explore/projects/trending.html.haml
index 1412b19acde..fa0b718e48b 100644
--- a/app/views/explore/projects/trending.html.haml
+++ b/app/views/explore/projects/trending.html.haml
@@ -7,7 +7,7 @@
= render 'explore/head'
.explore-trending-block
- .gray-content-block
+ .gray-content-block.second-block
.pull-right
= render 'explore/projects/dropdown'
.oneline
diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml
index 11d69977ef9..bbafc08435a 100644
--- a/app/views/groups/_projects.html.haml
+++ b/app/views/groups/_projects.html.haml
@@ -1,5 +1,5 @@
-.panel.panel-default.projects-list-holder
- .panel-heading.clearfix
+.projects-list-holder
+ .projects-search-form
.input-group
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false
- if can? current_user, :create_projects, @group
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index 8daac585960..1dea77c2e96 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -31,7 +31,7 @@
.col-sm-10
.checkbox
= f.check_box :public
- %span.descr Make this group public (even if there is no any public project inside this group)
+ %span.descr Make this group public (even if there are no public projects inside this group)
.form-actions
= f.submit 'Save group', class: "btn btn-save"
diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml
index 350e216fcc6..d063b257b5e 100644
--- a/app/views/groups/milestones/show.html.haml
+++ b/app/views/groups/milestones/show.html.haml
@@ -1,24 +1,24 @@
- page_title @milestone.title, "Milestones"
= render "header_title"
-.issuable-details
- .page-title
- .issue-box{ class: "issue-box-#{@milestone.closed? ? 'closed' : 'open'}" }
- - if @milestone.closed?
- Closed
- - else
- Open
+.detail-page-header
+ .status-box{ class: "status-box-#{@milestone.closed? ? 'closed' : 'open'}" }
+ - if @milestone.closed?
+ Closed
+ - else
+ Open
+ %span.identifier
Milestone #{@milestone.title}
- .pull-right
- - if can?(current_user, :admin_milestones, @group)
- - if @milestone.active?
- = link_to 'Close Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close"
- - else
- = link_to 'Reopen Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen"
+ .pull-right
+ - if can?(current_user, :admin_milestones, @group)
+ - if @milestone.active?
+ = link_to 'Close Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close"
+ - else
+ = link_to 'Reopen Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen"
- .gray-content-block.middle-block
- %h2.issue-title
- = markdown escape_once(@milestone.title), pipeline: :single_line
+.detail-page-description.gray-content-block.second-block
+ %h2.title
+ = markdown escape_once(@milestone.title), pipeline: :single_line
- if @milestone.complete? && @milestone.active?
.alert.alert-success.prepend-top-default
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index dc8e81323a6..c2c7c581b3e 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -5,37 +5,47 @@
- if current_user
= auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity")
-.dashboard
- .header-with-avatar.clearfix
- = image_tag group_icon(@group), class: "avatar group-avatar s90"
- %h3
- = @group.name
- .username
- @#{@group.path}
- - if @group.description.present?
- .description
- = markdown(@group.description, pipeline: :description)
- %hr
-
- = render 'shared/show_aside'
-
- - if can?(current_user, :read_group, @group)
- .row
- %section.activities.col-md-7
- .hidden-xs
- - if current_user
- = render "events/event_last_push", event: @last_push
- .pull-right
- = link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed", class: 'btn rss-btn' do
- %i.fa.fa-rss
-
- = render 'shared/event_filter'
- %hr
-
- .content_list
- = spinner
- %aside.side.col-md-5
- = render "projects", projects: @projects
- - else
- %p
- This group does not have public projects
+.cover-block
+ .avatar-holder
+ = link_to group_icon(@group), target: '_blank' do
+ = image_tag group_icon(@group), class: "avatar group-avatar s90"
+ .cover-title
+ = @group.name
+
+ .cover-desc.username
+ @#{@group.path}
+
+ - if @group.description.present?
+ .cover-desc.description
+ = markdown(@group.description, pipeline: :description)
+
+- if can?(current_user, :read_group, @group)
+ %ul.center-top-menu.no-top
+ %li.active
+ = link_to "#activity", 'data-toggle' => 'tab' do
+ Activity
+ - if @projects.present?
+ %li
+ = link_to "#projects", 'data-toggle' => 'tab' do
+ Projects
+
+ .tab-content
+ .tab-pane.active#activity
+ .gray-content-block.activity-filter-block
+ - if current_user
+ = render "events/event_last_push", event: @last_push
+ .pull-right
+ = link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed", class: 'btn rss-btn' do
+ %i.fa.fa-rss
+
+ = render 'shared/event_filter'
+
+ .content_list
+ = spinner
+
+ .tab-pane#projects
+ = render "projects", projects: @projects
+
+- else
+ %p
+ This group does not have public projects
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index 7e801b5332d..e8e331dd109 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -219,11 +219,3 @@
%td.shortcut
.key r
%td Reply (quoting selected text)
-
-
-:javascript
- $('.js-more-help-button').click(function (e) {
- $(this).remove()l
- $('.hidden-shortcut').show();
- e.preventDefault();
- });
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index 74174a72f5a..2e0bd2007a3 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -1,10 +1,26 @@
-- page_title "GitLab"
-%head
+%head{prefix: "og: http://ogp.me/ns#"}
%meta{charset: "utf-8"}
%meta{'http-equiv' => 'X-UA-Compatible', content: 'IE=edge'}
- %meta{content: "GitLab Community Edition", name: "description"}
%meta{name: 'referrer', content: 'origin-when-cross-origin'}
+ %meta{name: "description", content: page_description}
+
+ -# Open Graph - http://ogp.me/
+ %meta{property: 'og:type', content: "object"}
+ %meta{property: 'og:site_name', content: "GitLab"}
+ %meta{property: 'og:title', content: page_title}
+ %meta{property: 'og:description', content: page_description}
+ %meta{property: 'og:image', content: page_image}
+ %meta{property: 'og:url', content: request.base_url + request.fullpath}
+
+ -# Twitter Card - https://dev.twitter.com/cards/types/summary
+ %meta{property: 'twitter:card', content: "summary"}
+ %meta{property: 'twitter:title', content: page_title}
+ %meta{property: 'twitter:description', content: page_description}
+ %meta{property: 'twitter:image', content: page_image}
+ = page_card_meta_tags
+
+ - page_title "GitLab"
%title= page_title
= favicon_link_tag 'favicon.ico'
diff --git a/app/views/notify/_note_message.html.haml b/app/views/notify/_note_message.html.haml
index 27112c6745a..00cb4aa24cc 100644
--- a/app/views/notify/_note_message.html.haml
+++ b/app/views/notify/_note_message.html.haml
@@ -1,4 +1,2 @@
%div
- "#{link_to @note.author_name, user_url(@note.author)} wrote:"
-%div
= markdown(@note.note, pipeline: :email)
diff --git a/app/views/profiles/keys/new.html.haml b/app/views/profiles/keys/new.html.haml
index 11166dc6d99..13a18269d11 100644
--- a/app/views/profiles/keys/new.html.haml
+++ b/app/views/profiles/keys/new.html.haml
@@ -12,6 +12,6 @@
comment = val.match(/^\S+ \S+ (.+)\n?$/);
if( comment && comment.length > 1 && title.val() == '' ){
- $('#key_title').val( comment[1] );
+ $('#key_title').val( comment[1] ).change();
}
});
diff --git a/app/views/profiles/two_factor_auths/new.html.haml b/app/views/profiles/two_factor_auths/new.html.haml
index 92dc58c10d7..1a5b6efce35 100644
--- a/app/views/profiles/two_factor_auths/new.html.haml
+++ b/app/views/profiles/two_factor_auths/new.html.haml
@@ -38,3 +38,4 @@
= text_field_tag :pin_code, nil, class: "form-control", required: true, autofocus: true
.form-actions
= submit_tag 'Submit', class: 'btn btn-success'
+ = link_to 'Configure it later', skip_profile_two_factor_auth_path, :method => :patch, class: 'btn btn-cancel' if two_factor_skippable?
diff --git a/app/views/projects/_commit_button.html.haml b/app/views/projects/_commit_button.html.haml
index 2fd3d9e1be4..640612ca433 100644
--- a/app/views/projects/_commit_button.html.haml
+++ b/app/views/projects/_commit_button.html.haml
@@ -2,3 +2,7 @@
= button_tag 'Commit Changes', class: 'btn commit-btn js-commit-button btn-create'
= link_to 'Cancel', cancel_path,
class: 'btn btn-cancel', data: {confirm: leave_edit_message}
+
+ - unless can?(current_user, :push_code, @project)
+ .inline.prepend-left-10
+ = commit_in_fork_help
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index c1669ac046b..e92115b9b98 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -27,7 +27,7 @@
= icon('rss')
.project-repo-buttons
- .split-one
+ .split-one.count-buttons
= render 'projects/buttons/star'
= render 'projects/buttons/fork'
@@ -38,3 +38,6 @@
= render 'projects/buttons/dropdown'
= render 'projects/buttons/notifications'
+
+:coffeescript
+ new Star() \ No newline at end of file
diff --git a/app/views/projects/blob/_actions.html.haml b/app/views/projects/blob/_actions.html.haml
index b1df8d19938..cdac50f7a8d 100644
--- a/app/views/projects/blob/_actions.html.haml
+++ b/app/views/projects/blob/_actions.html.haml
@@ -2,7 +2,7 @@
= link_to 'Raw', namespace_project_raw_path(@project.namespace, @project, @id),
class: 'btn btn-sm', target: '_blank'
-# only show normal/blame view links for text files
- - if blob_viewable?(@blob)
+ - if blob_text_viewable?(@blob)
- if current_page? namespace_project_blame_path(@project.namespace, @project, @id)
= link_to 'Normal View', namespace_project_blob_path(@project.namespace, @project, @id),
class: 'btn btn-sm'
@@ -14,13 +14,8 @@
= link_to 'Permalink', namespace_project_blob_path(@project.namespace, @project,
tree_join(@commit.sha, @path)), class: 'btn btn-sm'
-- if blob_editable?(@blob)
+- if current_user
.btn-group{ role: "group" }
- = edit_blob_link(@project, @ref, @path)
- %button.btn.btn-default{ 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } Replace
- %button.btn.btn-remove{ 'data-target' => '#modal-remove-blob', 'data-toggle' => 'modal' } Delete
-- elsif !on_top_of_branch?
- .btn-group{ role: "group" }
- %button.btn.btn-default.disabled.has_tooltip{title: "You can only edit files when you are on a branch.", data: {container: 'body'}} Edit
- %button.btn.btn-default.disabled.has_tooltip{title: "You can only replace files when you are on a branch.", data: {container: 'body'}} Replace
- %button.btn.btn-remove.disabled.has_tooltip{title: "You can only delete files when you are on a branch.", data: {container: 'body'}} Delete
+ = edit_blob_link
+ = replace_blob_link
+ = delete_blob_link
diff --git a/app/views/projects/blob/_new_dir.html.haml b/app/views/projects/blob/_new_dir.html.haml
index fc6c9f5fd09..084608bbba3 100644
--- a/app/views/projects/blob/_new_dir.html.haml
+++ b/app/views/projects/blob/_new_dir.html.haml
@@ -17,5 +17,9 @@
= submit_tag "Create directory", class: 'btn btn-create'
= link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
+ - unless can?(current_user, :push_code, @project)
+ .inline.prepend-left-10
+ = commit_in_fork_help
+
:javascript
new NewCommitForm($('.js-create-dir-form'))
diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml
index ecc90a30e78..676924dc6ca 100644
--- a/app/views/projects/blob/_upload.html.haml
+++ b/app/views/projects/blob/_upload.html.haml
@@ -20,6 +20,11 @@
= button_tag button_title, class: 'btn btn-small btn-create btn-upload-file', id: 'submit-all'
= link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
+ - unless can?(current_user, :push_code, @project)
+ .inline.prepend-left-10
+ = commit_in_fork_help
+
+
:javascript
disableButtonIfEmptyField($('.js-upload-blob-form').find('.js-commit-message'), '.btn-upload-file');
new BlobFileDropzone($('.js-upload-blob-form'), '#{method}');
diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml
index a47fe7ede80..09fa148b129 100644
--- a/app/views/projects/blob/edit.html.haml
+++ b/app/views/projects/blob/edit.html.haml
@@ -20,7 +20,7 @@
= hidden_field_tag 'last_commit', @last_commit
= hidden_field_tag 'content', '', id: "file-content"
= hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id]
- = render 'projects/commit_button', ref: @ref, cancel_path: @after_edit_path
+ = render 'projects/commit_button', ref: @ref, cancel_path: namespace_project_blob_path(@project.namespace, @project, @id)
:javascript
blob = new EditBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", "#{@blob.language.try(:ace_mode)}")
diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml
index 3f8d11ed8c8..6988039b6c7 100644
--- a/app/views/projects/blob/show.html.haml
+++ b/app/views/projects/blob/show.html.haml
@@ -6,7 +6,7 @@
%div#tree-holder.tree-holder
= render 'blob', blob: @blob
-- if blob_editable?(@blob)
+- if can_edit_blob?(@blob)
= render 'projects/blob/remove'
- title = "Replace #{@blob.name}"
diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml
index 31943a2407a..c659af6338c 100644
--- a/app/views/projects/branches/new.html.haml
+++ b/app/views/projects/branches/new.html.haml
@@ -9,11 +9,12 @@
New Branch
%hr
-= form_tag namespace_project_branches_path, method: :post, id: "new-branch-form", class: "form-horizontal js-requires-input" do
+= form_tag namespace_project_branches_path, method: :post, id: "new-branch-form", class: "form-horizontal js-create-branch-form js-requires-input" do
.form-group
= label_tag :branch_name, nil, class: 'control-label'
.col-sm-10
- = text_field_tag :branch_name, params[:branch_name], required: true, tabindex: 1, autofocus: true, class: 'form-control'
+ = text_field_tag :branch_name, params[:branch_name], required: true, tabindex: 1, autofocus: true, class: 'form-control js-branch-name'
+ .help-block.text-danger.js-branch-name-error
.form-group
= label_tag :ref, 'Create from', class: 'control-label'
.col-sm-10
@@ -26,7 +27,4 @@
:javascript
var availableRefs = #{@project.repository.ref_names.to_json};
- $("#ref").autocomplete({
- source: availableRefs,
- minLength: 1
- });
+ new NewBranchForm($('.js-create-branch-form'), availableRefs)
diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml
index 20a5b6a66e7..5b7ecce86ab 100644
--- a/app/views/projects/builds/show.html.haml
+++ b/app/views/projects/builds/show.html.haml
@@ -7,6 +7,10 @@
%strong.monospace= link_to @build.commit.short_sha, ci_status_path(@build.commit)
from
= link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref)
+ - merge_request = @build.merge_request
+ - if merge_request
+ via
+ = link_to "merge request ##{merge_request.iid}", merge_request_path(merge_request)
#up-build-trace
- if @commit.matrix_for_ref?(@build.ref)
diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml
index b277b765b6b..1f639fecc30 100644
--- a/app/views/projects/buttons/_dropdown.html.haml
+++ b/app/views/projects/buttons/_dropdown.html.haml
@@ -18,10 +18,11 @@
= link_to new_namespace_project_snippet_path(@project.namespace, @project) do
= icon('file-text-o fw')
New snippet
+
- if can?(current_user, :push_code, @project)
%li.divider
%li
- = link_to namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master'), title: 'New file' do
+ = link_to namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master') do
= icon('file fw')
New file
%li
@@ -32,3 +33,20 @@
= link_to new_namespace_project_tag_path(@project.namespace, @project) do
= icon('tags fw')
New tag
+ - elsif current_user && current_user.already_forked?(@project)
+ %li.divider
+ %li
+ = link_to namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master') do
+ = icon('file fw')
+ New file
+ - elsif can?(current_user, :fork_project, @project)
+ %li.divider
+ %li
+ - continue_params = { to: namespace_project_new_blob_path(@project.namespace, @project, @project.default_branch || 'master'),
+ notice: edit_in_new_fork_notice,
+ notice_now: edit_in_new_fork_notice_now }
+ - fork_path = namespace_project_fork_path(@project.namespace, @project, namespace_key: current_user.namespace.id,
+ continue: continue_params)
+ = link_to fork_path, method: :post do
+ = icon('file fw')
+ New file
diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml
index 2d3abf09051..133531887a2 100644
--- a/app/views/projects/buttons/_fork.html.haml
+++ b/app/views/projects/buttons/_fork.html.haml
@@ -4,10 +4,15 @@
= 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')
Fork
+ %div.count-with-arrow
+ %span.arrow
%span.count
= @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')
+ Fork
+ %div.count-with-arrow
+ %span.arrow
%span.count
= @project.forks_count
diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml
index 41a3ec6d90f..21ba426aaa1 100644
--- a/app/views/projects/buttons/_star.html.haml
+++ b/app/views/projects/buttons/_star.html.haml
@@ -1,19 +1,21 @@
- if current_user
= link_to toggle_star_namespace_project_path(@project.namespace, @project), class: 'btn star-btn toggle-star has_tooltip', method: :post, remote: true, title: "Star project" do
- = icon('star fw')
- %span.count
+ - if current_user.starred?(@project)
+ = icon('star fw')
+ %span.starred Unstar
+ - else
+ = icon('star-o fw')
+ %span Star
+ %div.count-with-arrow
+ %span.arrow
+ %span.count.star-count
= @project.star_count
- :javascript
- $('.project-home-panel .toggle-star').on('ajax:success', function (e, data, status, xhr) {
- $(this).replaceWith(data.html);
- })
- .on('ajax:error', function (e, xhr, status, error) {
- new Flash('Star toggle failed. Try again later.', 'alert');
- });
-
- else
= link_to new_user_session_path, class: 'btn has_tooltip star-btn', title: 'You must sign in to star a project' do
= icon('star fw')
+ Star
+ %div.count-with-arrow
+ %span.arrow
%span.count
= @project.star_count
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 634924db247..ddb77fd796b 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -20,8 +20,8 @@
%p
%span.light Commit
- = link_to @commit.id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace", data: { clipboard_text: @commit.id }
- = clipboard_button
+ = link_to @commit.id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace"
+ = clipboard_button(clipboard_text: @commit.id)
.commit-info-row
%span.light Authored by
%strong
@@ -40,7 +40,7 @@
- @commit.parents.each do |parent|
= link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent), class: "monospace"
-- if @ci_commit && @ci_commit.show_build_status?
+- if @ci_commit
.pull-right
= link_to ci_status_path(@ci_commit), class: "ci-status ci-#{@ci_commit.status}" do
= ci_status_icon(@ci_commit)
diff --git a/app/views/projects/commit_statuses/_commit_status.html.haml b/app/views/projects/commit_statuses/_commit_status.html.haml
index 45a00e4d259..74a05df24d3 100644
--- a/app/views/projects/commit_statuses/_commit_status.html.haml
+++ b/app/views/projects/commit_statuses/_commit_status.html.haml
@@ -19,11 +19,11 @@
- if defined?(commit_sha) && commit_sha
%td
- = link_to commit_status.short_sha, namespace_project_commit_path(@project.namespace, @project, commit_status.sha), class: "monospace"
-
+ = link_to commit_status.short_sha, namespace_project_commit_path(commit_status.project.namespace, commit_status.project, commit_status.sha), class: "monospace"
+
%td
- if commit_status.ref
- = link_to commit_status.ref, namespace_project_commits_path(@project.namespace, @project, commit_status.ref)
+ = link_to commit_status.ref, namespace_project_commits_path(commit_status.project.namespace, commit_status.project, commit_status.ref)
- else
.light none
@@ -66,7 +66,7 @@
%td
.pull-right
- - if current_user && can?(current_user, :download_build_artifacts, @project) && commit_status.download_url
+ - if current_user && can?(current_user, :download_build_artifacts, commit_status.project) && commit_status.download_url
= link_to commit_status.download_url, title: 'Download artifacts' do
%i.fa.fa-download
- if current_user && can?(current_user, :manage_builds, commit_status.project)
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 1303b27c4f3..28b82dd31f3 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -17,7 +17,7 @@
%a.text-expander.js-toggle-button ...
.pull-right
- - if ci_commit && ci_commit.show_build_status?
+ - if ci_commit
= render_ci_status(ci_commit)
&nbsp;
= clipboard_button(clipboard_text: commit.id)
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index 327e7d9245a..517f6aef7c5 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -24,7 +24,7 @@
= "#{diff_file.diff.a_mode} → #{diff_file.diff.b_mode}"
.diff-controls
- - if blob_viewable?(blob)
+ - if blob_text_viewable?(blob)
= link_to '#', class: 'js-toggle-diff-comments btn btn-sm active has_tooltip', title: "Toggle comments for this file" do
%i.fa.fa-comments
&nbsp;
@@ -32,14 +32,15 @@
- if editable_diff?(diff_file)
= edit_blob_link(@merge_request.source_project,
@merge_request.source_branch, diff_file.new_path,
- after: '&nbsp;', from_merge_request_id: @merge_request.id)
+ from_merge_request_id: @merge_request.id)
+ &nbsp;
= view_file_btn(diff_commit.id, diff_file, project)
.diff-content.diff-wrap-lines
-# Skipp all non non-supported blobs
- return unless blob.respond_to?('text?')
- - if blob_viewable?(blob)
+ - if blob_text_viewable?(blob)
- if diff_view == 'parallel'
= render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i
- else
diff --git a/app/views/projects/forks/new.html.haml b/app/views/projects/forks/new.html.haml
index f0b0a11c04a..8a2c027a455 100644
--- a/app/views/projects/forks/new.html.haml
+++ b/app/views/projects/forks/new.html.haml
@@ -43,4 +43,3 @@
%i.fa.fa-spinner.fa-spin
Forking repository
%p Please wait a moment, this page will automatically refresh when ready.
-
diff --git a/app/views/projects/issues/_closed_by_box.html.haml b/app/views/projects/issues/_closed_by_box.html.haml
index 3c491c1a8b8..de415ae51a4 100644
--- a/app/views/projects/issues/_closed_by_box.html.haml
+++ b/app/views/projects/issues/_closed_by_box.html.haml
@@ -1,3 +1,2 @@
-.issue-closed-by-widget
- = icon('check')
+.issue-closed-by-widget.gray-content-block.second-block.white
This issue will be closed automatically when merge request #{markdown(merge_requests_sentence(@closed_by_merge_requests), pipeline: :gfm)} is accepted.
diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml
index 405bae1bbb9..dc434cf38c4 100644
--- a/app/views/projects/issues/_discussion.html.haml
+++ b/app/views/projects/issues/_discussion.html.haml
@@ -1,12 +1,9 @@
- content_for :note_actions do
- if can?(current_user, :update_issue, @issue)
- if @issue.closed?
- = link_to 'Reopen Issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true), method: :put, class: 'btn btn-grouped btn-reopen js-note-target-reopen', title: 'Reopen Issue'
+ = link_to 'Reopen Issue', issue_path(@issue, issue: {state_event: :reopen}, status_only: true), method: :put, class: 'btn btn-nr btn-grouped btn-reopen js-note-target-reopen', title: 'Reopen Issue'
- else
- = link_to 'Close Issue', issue_path(@issue, issue: {state_event: :close}, status_only: true), method: :put, class: 'btn btn-grouped btn-close js-note-target-close', title: 'Close Issue'
-
-.gray-content-block.second-block.oneline-block
- = render 'votes/votes_block', votable: @issue
+ = link_to 'Close Issue', issue_path(@issue, issue: {state_event: :close}, status_only: true), method: :put, class: 'btn btn-nr btn-grouped btn-close js-note-target-close', title: 'Close Issue'
#notes
= render 'projects/notes/notes_with_form'
diff --git a/app/views/projects/issues/_merge_requests.html.haml b/app/views/projects/issues/_merge_requests.html.haml
index fe856ac991e..254968e4f67 100644
--- a/app/views/projects/issues/_merge_requests.html.haml
+++ b/app/views/projects/issues/_merge_requests.html.haml
@@ -15,9 +15,10 @@
%span.merge-request-info
%strong
= link_to_gfm merge_request.title, merge_request_path(merge_request), class: "row_title"
- in
- - project = merge_request.target_project
- = link_to project.name_with_namespace, namespace_project_path(project.namespace, project)
+ - unless @issue.project.id == merge_request.target_project.id
+ in
+ - project = merge_request.target_project
+ = link_to project.name_with_namespace, namespace_project_path(project.namespace, project)
%span.merge-request-status.prepend-left-10
- if merge_request.merged?
MERGED
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index cc2cf8c8716..f931a0d3b92 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -1,61 +1,64 @@
-- page_title "#{@issue.title} (##{@issue.iid})", "Issues"
+- page_title "#{@issue.title} (##{@issue.iid})", "Issues"
+- page_description @issue.description
+- page_card_attributes @issue.card_attributes
+
= render "header_title"
.issue
+ .detail-page-header
+ .status-box{ class: "status-box-closed #{issue_button_visibility(@issue, false)}"} Closed
+ .status-box{ class: "status-box-open #{issue_button_visibility(@issue, true)}"} Open
+ %span.identifier
+ Issue ##{@issue.iid}
+ %span.creator
+ &middot;
+ opened by #{link_to_member(@project, @issue.author, size: 24)}
+ &middot;
+ = time_ago_with_tooltip(@issue.created_at, placement: 'bottom', html_class: 'issue_created_ago')
+ - if @issue.updated_at != @issue.created_at
+ %span
+ &middot;
+ = icon('edit', title: 'edited')
+ = time_ago_with_tooltip(@issue.updated_at, placement: 'bottom', html_class: 'issue_edited_ago')
+
+ .pull-right
+ - if can?(current_user, :create_issue, @project)
+ = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'btn btn-nr btn-grouped new-issue-link btn-success', title: 'New Issue', id: 'new_issue_link' do
+ = icon('plus')
+ New Issue
+ - if can?(current_user, :update_issue, @issue)
+ = link_to 'Reopen', issue_path(@issue, issue: {state_event: :reopen}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen Issue'
+ = link_to 'Close', issue_path(@issue, issue: {state_event: :close}, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "btn btn-nr btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close Issue'
+
+ = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'btn btn-nr btn-grouped issuable-edit' do
+ = icon('pencil-square-o')
+ Edit
+
.issue-details.issuable-details
- .issuable-title
- .issue-box{ class: issue_box_class(@issue) }
- - if @issue.closed?
- Closed
- - else
- Open
- %span.issuable-id Issue ##{@issue.iid}
- %span.creator
- &middot;
- opened by #{link_to_member(@project, @issue.author, size: 24)}
- &middot;
- = time_ago_with_tooltip(@issue.created_at, placement: 'bottom', html_class: 'issue_created_ago')
- - if @issue.updated_at != @issue.created_at
- %span
- &middot;
- = icon('edit', title: 'edited')
- = time_ago_with_tooltip(@issue.updated_at, placement: 'bottom', html_class: 'issue_edited_ago')
+ .detail-page-description.gray-content-block.second-block
+ %h2.title
+ = markdown escape_once(@issue.title), pipeline: :single_line
+ %div
+ - if @issue.description.present?
+ .description{class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : ''}
+ .wiki
+ = preserve do
+ = markdown(@issue.description, cache_key: [@issue, "description"])
+ %textarea.hidden.js-task-list-field
+ = @issue.description
- .pull-right
- - if can?(current_user, :create_issue, @project)
- = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'btn btn-grouped new-issue-link', title: 'New Issue', id: 'new_issue_link' do
- = icon('plus')
- New Issue
- - if can?(current_user, :update_issue, @issue)
- - if @issue.closed?
- = link_to 'Reopen', issue_path(@issue, issue: {state_event: :reopen}, status_only: true), method: :put, class: 'btn btn-grouped btn-reopen'
- - else
- = link_to 'Close', issue_path(@issue, issue: {state_event: :close}, status_only: true), method: :put, class: 'btn btn-grouped btn-close', title: 'Close Issue'
+ .merge-requests
+ = render 'merge_requests'
- = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'btn btn-grouped issuable-edit' do
- = icon('pencil-square-o')
- Edit
+ .gray-content-block.second-block.oneline-block
+ = render 'votes/votes_block', votable: @issue
+
+ - if @closed_by_merge_requests.present?
+ = render 'projects/issues/closed_by_box'
.row
%section.col-md-9
- .gray-content-block
- %h2.issue-title
- = markdown escape_once(@issue.title), pipeline: :single_line
- %div
- - if @issue.description.present?
- .description{class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : ''}
- .wiki
- = preserve do
- = markdown(@issue.description, cache_key: [@issue, "description"])
- %textarea.hidden.js-task-list-field
- = @issue.description
-
- .merge-requests
- = render 'merge_requests'
-
- - if @closed_by_merge_requests.present?
- = render 'projects/issues/closed_by_box'
- .issue-discussion
+ .issuable-discussion
= render 'projects/issues/discussion'
%aside.col-md-3
diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml
index 7a7428d35cc..bff3c3b283d 100644
--- a/app/views/projects/merge_requests/_discussion.html.haml
+++ b/app/views/projects/merge_requests/_discussion.html.haml
@@ -1,11 +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_path(@merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close close-mr-link js-note-target-close", title: "Close merge request"
+ = link_to 'Close', merge_request_path(@merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-nr btn-grouped btn-close close-mr-link js-note-target-close", title: "Close merge request"
- if @merge_request.closed?
- = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request"
-
-.gray-content-block.second-block.oneline-block
- = render 'votes/votes_block', votable: @merge_request
+ = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-nr btn-grouped btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request"
#notes= render "projects/notes/notes_with_form"
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index 105c731c7e1..a051729dc32 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -17,7 +17,7 @@
- if merge_request.open? && merge_request.broken?
%li
- = link_to merge_request_path(merge_request), class: "has_tooltip", title: "Cannot be merged automatically", data: {container: 'body'} do
+ = link_to merge_request_path(merge_request), class: "has_tooltip", title: "Cannot be merged automatically", data: { container: 'body' } do
= icon('exclamation-triangle')
- if merge_request.assignee
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index 4172d5a4e88..a14943b15d3 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -23,15 +23,15 @@
= link_to url_for(params), data: {target: 'div#commits', action: 'commits', toggle: 'tab'} do
Commits
%span.badge= @commits.size
- %li.diffs-tab.active
- = link_to url_for(params), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do
- Changes
- %span.badge= @diffs.size
- if @ci_commit
%li.builds-tab.active
= link_to url_for(params), data: {target: 'div#builds', action: 'builds', toggle: 'tab'} do
Builds
%span.badge= @statuses.size
+ %li.diffs-tab.active
+ = link_to url_for(params), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do
+ Changes
+ %span.badge= @diffs.size
.tab-content
#commits.commits.tab-pane
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 04f8fd74422..ba7c2c01e93 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -1,85 +1,91 @@
-- page_title "#{@merge_request.title} (##{@merge_request.iid})", "Merge Requests"
+- page_title "#{@merge_request.title} (##{@merge_request.iid})", "Merge Requests"
+- page_description @merge_request.description
+- page_card_attributes @merge_request.card_attributes
+
= render "header_title"
- if params[:view] == 'parallel'
- fluid_layout true
.merge-request{'data-url' => merge_request_path(@merge_request)}
- .merge-request-details.issuable-details
- = render "projects/merge_requests/show/mr_title"
- .row
- %section.col-md-9
- = render "projects/merge_requests/show/mr_box"
- .append-bottom-default.mr-source-target.prepend-top-default
- - if @merge_request.open?
- .pull-right
- - if @merge_request.source_branch_exists?
- = link_to "#modal_merge_info", class: "btn btn-sm", "data-toggle" => "modal" do
- = icon('cloud-download fw')
- Check out branch
+ = render "projects/merge_requests/show/mr_title"
- %span.dropdown
- %a.btn.btn-sm.dropdown-toggle{ data: {toggle: :dropdown} }
- = icon('download')
- Download as
- %span.caret
- %ul.dropdown-menu
- %li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch)
- %li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff)
- .normal
- %span Request to merge
- %span.label-branch= source_branch_with_namespace(@merge_request)
- %span into
- = link_to namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" do
- = @merge_request.target_branch
+ .merge-request-details.issuable-details
+ = render "projects/merge_requests/show/mr_box"
+ .append-bottom-default.mr-source-target.prepend-top-default
+ - if @merge_request.open?
+ .pull-right
+ - if @merge_request.source_branch_exists?
+ = link_to "#modal_merge_info", class: "btn btn-sm", "data-toggle" => "modal" do
+ = icon('cloud-download fw')
+ Check out branch
- = render "projects/merge_requests/show/how_to_merge"
- = render "projects/merge_requests/widget/show.html.haml"
+ %span.dropdown
+ %a.btn.btn-sm.dropdown-toggle{ data: {toggle: :dropdown} }
+ = icon('download')
+ Download as
+ %span.caret
+ %ul.dropdown-menu
+ %li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch)
+ %li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff)
+ .normal
+ %span Request to merge
+ %span.label-branch= source_branch_with_namespace(@merge_request)
+ %span into
+ = link_to namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" do
+ = @merge_request.target_branch
- - if @merge_request.open? && @merge_request.source_branch_exists? && @merge_request.can_be_merged? && @merge_request.can_be_merged_by?(current_user)
- .light.prepend-top-default
- You can also accept this merge request manually using the
- = succeed '.' do
- = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
+ = render "projects/merge_requests/show/how_to_merge"
+ = render "projects/merge_requests/widget/show.html.haml"
- - if @commits.present?
- %ul.merge-request-tabs.center-top-menu.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
- Discussion
- %span.badge= @merge_request.mr_and_commit_notes.user.count
- %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
- %li.diffs-tab
- = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do
- Changes
- %span.badge= @merge_request.diffs.size
- - if @ci_commit
- %li.builds-tab
- = link_to builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#builds', action: 'builds', toggle: 'tab'} do
- Builds
- %span.badge= @statuses.size
+ - if @merge_request.open? && @merge_request.source_branch_exists? && @merge_request.can_be_merged? && @merge_request.can_be_merged_by?(current_user)
+ .light.prepend-top-default
+ You can also accept this merge request manually using the
+ = succeed '.' do
+ = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
- .tab-content
- #notes.notes.tab-pane.voting_notes
- = render "projects/merge_requests/discussion"
- #commits.commits.tab-pane
- - # This tab is always loaded via AJAX
- #diffs.diffs.tab-pane
- - # This tab is always loaded via AJAX
- #builds.builds.tab-pane
- - # This tab is always loaded via AJAX
+ - if @commits.present?
+ %ul.merge-request-tabs.center-top-menu.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
+ Discussion
+ %span.badge= @merge_request.mr_and_commit_notes.user.count
+ %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
+ - if @ci_commit
+ %li.builds-tab
+ = link_to builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#builds', action: 'builds', toggle: 'tab'} do
+ Builds
+ %span.badge= @statuses.size
+ %li.diffs-tab
+ = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do
+ Changes
+ %span.badge= @merge_request.diffs.size
- .mr-loading-status
- = spinner
+ .tab-content
+ #notes.notes.tab-pane.voting_notes
+ .gray-content-block.second-block.oneline-block
+ = render 'votes/votes_block', votable: @merge_request
- %aside.col-md-3
- = render 'shared/issuable/sidebar', issuable: @merge_request
+ .row
+ %section.col-md-9
+ .issuable-discussion
+ = render "projects/merge_requests/discussion"
+ %aside.col-md-3
+ = render 'shared/issuable/sidebar', issuable: @merge_request
+ = render 'shared/show_aside'
- = render 'shared/show_aside'
+ #commits.commits.tab-pane
+ - # This tab is always loaded via AJAX
+ #builds.builds.tab-pane
+ - # This tab is always loaded via AJAX
+ #diffs.diffs.tab-pane
+ - # This tab is always loaded via AJAX
+ .mr-loading-status
+ = spinner
:javascript
var merge_request;
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 98f0357ce4e..877cc3d744b 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,8 +8,8 @@
%p
%strong Step 1.
Fetch and check out the branch for this merge request
- = clipboard_button
- %pre.dark
+ = clipboard_button(clipboard_target: 'pre#merge-info-1')
+ %pre.dark#merge-info-1
- if @merge_request.for_fork?
:preserve
git fetch #{h @merge_request.source_project.http_url_to_repo} #{h @merge_request.source_branch}
@@ -25,8 +25,8 @@
%p
%strong Step 3.
Merge the branch and fix any conflicts that come up
- = clipboard_button
- %pre.dark
+ = clipboard_button(clipboard_target: 'pre#merge-info-3')
+ %pre.dark#merge-info-3
- if @merge_request.for_fork?
:preserve
git checkout #{h @merge_request.target_branch}
@@ -38,8 +38,8 @@
%p
%strong Step 4.
Push the result of the merge to GitLab
- = clipboard_button
- %pre.dark
+ = clipboard_button(clipboard_target: 'pre#merge-info-4')
+ %pre.dark#merge-info-4
:preserve
git push origin #{h @merge_request.target_branch}
- unless @merge_request.can_be_merged_by?(current_user)
diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml
index 9bfe202589e..0f81e5e8914 100644
--- a/app/views/projects/merge_requests/show/_mr_box.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_box.html.haml
@@ -1,5 +1,5 @@
-.gray-content-block.middle-block
- %h2.issue-title
+.detail-page-description.gray-content-block.second-block
+ %h2.title
= markdown escape_once(@merge_request.title), pipeline: :single_line
%div
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 d65c3b16618..fc6fb2a0d42 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -1,7 +1,8 @@
-.issuable-title
- .issue-box{ class: issue_box_class(@merge_request) }
+.detail-page-header
+ .status-box{ class: status_box_class(@merge_request) }
= @merge_request.state_human_name
- %span.issuable-id Merge Request ##{@merge_request.iid}
+ %span.identifier
+ Merge Request ##{@merge_request.iid}
%span.creator
&middot;
opened by #{link_to_member(@project, @merge_request.author, size: 24)}
@@ -16,9 +17,9 @@
.issue-btn-group.pull-right
- if can?(current_user, :update_merge_request, @merge_request)
- if @merge_request.open?
- = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "btn btn-grouped btn-close", title: "Close merge request"
- = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "btn btn-grouped issuable-edit", id: "edit_merge_request" do
+ = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: 'btn btn-nr btn-grouped btn-close', title: 'Close merge request'
+ = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: 'btn btn-nr btn-grouped issuable-edit', id: 'edit_merge_request' do
%i.fa.fa-pencil-square-o
Edit
- if @merge_request.closed?
- = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link", title: "Close merge request"
+ = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: 'btn btn-nr btn-grouped btn-reopen reopen-mr-link', title: 'Reopen merge request'
diff --git a/app/views/projects/merge_requests/widget/_merged.html.haml b/app/views/projects/merge_requests/widget/_merged.html.haml
index 6f52c963a53..d1d602eecdc 100644
--- a/app/views/projects/merge_requests/widget/_merged.html.haml
+++ b/app/views/projects/merge_requests/widget/_merged.html.haml
@@ -8,19 +8,15 @@
#{time_ago_with_tooltip(@merge_request.merge_event.created_at)}
%div
- if !@merge_request.source_branch_exists? || (params[:delete_source] == 'true')
- = succeed '.' do
- The changes were merged into
- = link_to namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" do
- = @merge_request.target_branch
+ The changes were merged into
+ #{link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch"}.
The source branch has been removed.
- elsif @merge_request.can_remove_source_branch?(current_user)
.remove_source_branch_widget
%p
- = succeed '.' do
- The changes were merged into
- = link_to namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch" do
- = @merge_request.target_branch
+ The changes were merged into
+ #{link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch), class: "label-branch"}.
You can remove the source branch now.
= link_to namespace_project_branch_path(@merge_request.source_project.namespace, @merge_request.source_project, @merge_request.source_branch), remote: true, method: :delete, class: "btn btn-primary btn-sm remove_source_branch" do
%i.fa.fa-times
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 c6bc4ca5beb..d9a1730a8bc 100644
--- a/app/views/projects/merge_requests/widget/open/_accept.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml
@@ -7,13 +7,13 @@
.accept-action
- if @ci_commit && @ci_commit.active?
%span.btn-group
- = link_to "#", class: "btn btn-create merge_when_build_succeeds" do
+ = button_tag class: "btn btn-create js-merge-button merge_when_build_succeeds" do
Merge When Build Succeeds
- %a.btn.btn-success.dropdown-toggle{ 'data-toggle' => 'dropdown' }
+ = button_tag class: "btn btn-success dropdown-toggle", 'data-toggle' => 'dropdown' do
%span.caret
%span.sr-only
Select Merge Moment
- %ul.dropdown-menu.dropdown-menu-right{ role: 'menu' }
+ %ul.js-merge-dropdown.dropdown-menu.dropdown-menu-right{ role: 'menu' }
%li
= link_to "#", class: "merge_when_build_succeeds" do
= icon('check fw')
@@ -23,7 +23,7 @@
= icon('warning fw')
Merge Immediately
- else
- = f.button class: "btn btn-create btn-grouped accept_merge_request #{status_class}" do
+ = f.button class: "btn btn-create btn-grouped js-merge-button accept_merge_request #{status_class}" do
Accept Merge Request
- if @merge_request.can_remove_source_branch?(current_user)
.accept-control.checkbox
@@ -42,21 +42,19 @@
= hidden_field_tag :merge_when_build_succeeds, "", autocomplete: "off"
:javascript
- $('.accept_merge_request').on('click', function() {
- $(this).html("<i class='fa fa-spinner fa-spin'></i> Merge in progress");
- });
-
$('.accept-mr-form').on('ajax:send', function() {
$(".accept-mr-form :input").disable();
});
- $('a.accept_merge_request').on('click', function(e) {
- e.preventDefault();
- $(this).closest("form").submit();
+ $('.accept_merge_request').on('click', function() {
+ $('.js-merge-button').html("<i class='fa fa-spinner fa-spin'></i> Merge in progress");
});
- $('a.merge_when_build_succeeds').on('click', function(e) {
- e.preventDefault();
+ $('.merge_when_build_succeeds').on('click', function() {
$("#merge_when_build_succeeds").val("1");
+ });
+
+ $('.js-merge-dropdown a').on('click', function(e) {
+ e.preventDefault();
$(this).closest("form").submit();
});
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 7ecee440337..1670ea8741a 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -1,44 +1,46 @@
-- page_title @milestone.title, "Milestones"
+- page_title @milestone.title, "Milestones"
+- page_description @milestone.description
+
= render "header_title"
-.issuable-details
- .page-title
- .issue-box{ class: issue_box_class(@milestone) }
- - if @milestone.closed?
- Closed
- - elsif @milestone.expired?
- Expired
- - else
- Open
+.detail-page-header
+ .status-box{ class: status_box_class(@milestone) }
+ - if @milestone.closed?
+ Closed
+ - elsif @milestone.expired?
+ Expired
+ - else
+ Open
+ %span.identifier
Milestone ##{@milestone.iid}
- - if @milestone.expires_at
- %span.creator
- &middot;
- = @milestone.expires_at
- .pull-right
- - if can?(current_user, :admin_milestone, @project)
- = link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped" do
- %i.fa.fa-pencil-square-o
- Edit
-
- - if @milestone.active?
- = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-grouped"
- - else
- = link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-grouped"
-
- = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-remove" do
- %i.fa.fa-trash-o
- Delete
-
- .gray-content-block.middle-block
- %h2.issue-title
- = markdown escape_once(@milestone.title), pipeline: :single_line
- %div
- - if @milestone.description.present?
- .description
- .wiki
- = preserve do
- = markdown @milestone.description
+ - if @milestone.expires_at
+ %span.creator
+ &middot;
+ = @milestone.expires_at
+ .pull-right
+ - if can?(current_user, :admin_milestone, @project)
+ - if @milestone.active?
+ = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-grouped"
+ - else
+ = link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-grouped"
+
+ = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-remove" do
+ %i.fa.fa-trash-o
+ Delete
+
+ = link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped" do
+ %i.fa.fa-pencil-square-o
+ Edit
+
+.detail-page-description.gray-content-block.second-block
+ %h2.title
+ = markdown escape_once(@milestone.title), pipeline: :single_line
+ %div
+ - if @milestone.description.present?
+ .description
+ .wiki
+ = preserve do
+ = markdown @milestone.description
- if @milestone.issues.any? && @milestone.can_be_closed?
.alert.alert-success.prepend-top-default
diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml
index 88e711ab534..acb6dc52a8e 100644
--- a/app/views/projects/notes/_form.html.haml
+++ b/app/views/projects/notes/_form.html.haml
@@ -13,6 +13,6 @@
.error-alert
.note-form-actions.clearfix
- = f.submit 'Add Comment', class: "btn btn-create comment-btn btn-grouped js-comment-button"
+ = f.submit 'Add Comment', class: "btn btn-nr btn-create comment-btn btn-grouped js-comment-button"
= yield(:note_actions)
%a.btn.btn-cancel.js-close-discussion-note-form Cancel
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 9c7a5584da9..7466a098e24 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -71,7 +71,7 @@
= render default_project_view
- if current_user
- - access = user_max_access_in_project(current_user, @project)
+ - access = user_max_access_in_project(current_user.id, @project)
- if access
.prepend-top-20.project-footer
.gray-content-block.footer-block.center
diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml
index 1bc90edd8f0..1927883513a 100644
--- a/app/views/projects/tree/_tree_content.html.haml
+++ b/app/views/projects/tree/_tree_content.html.haml
@@ -29,7 +29,7 @@
- if tree.readme
= render "projects/tree/readme", readme: tree.readme
-- if allowed_tree_edit?
+- if can_edit_tree?
= render 'projects/blob/upload', title: 'Upload New File', placeholder: 'Upload new file', button_title: 'Upload file', form_path: namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post
= render 'projects/blob/new_dir'
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index cefe33e581f..3343288ad2b 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -11,26 +11,65 @@
= link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, path)
- else
= link_to title, '#'
- - if allowed_tree_edit?
+
+ - if current_user
%li
- %span.dropdown
- %a.dropdown-toggle.btn.btn-sm.add-to-tree{href: '#', "data-toggle" => "dropdown"}
+ - if !on_top_of_branch?
+ %span.btn.btn-sm.add-to-tree.disabled.has_tooltip{title: "You can only add files when you are on a branch", data: { container: 'body' }}
= icon('plus')
- %ul.dropdown-menu
- %li
- = link_to namespace_project_new_blob_path(@project.namespace, @project, @id), title: 'Create file', id: 'new-file-link' do
- = icon('pencil fw')
- Create file
- %li
- = link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal'} do
- = icon('file fw')
- Upload file
- %li.divider
- %li
- = link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal'} do
- = icon('folder fw')
- New directory
- - elsif !on_top_of_branch?
- %li
- %span.btn.btn-sm.add-to-tree.disabled.has_tooltip{title: "You can only add files when you are on a branch.", data: {container: 'body'}}
- = icon('plus')
+ - else
+ %span.dropdown
+ %a.dropdown-toggle.btn.btn-sm.add-to-tree{href: '#', "data-toggle" => "dropdown"}
+ = icon('plus')
+ %ul.dropdown-menu
+ - if can_edit_tree?
+ %li
+ = link_to namespace_project_new_blob_path(@project.namespace, @project, @id) do
+ = icon('pencil fw')
+ New file
+ %li
+ = link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal'} do
+ = icon('file fw')
+ Upload file
+ %li
+ = link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal'} do
+ = icon('folder fw')
+ New directory
+ - elsif can?(current_user, :fork_project, @project)
+ %li
+ - continue_params = { to: namespace_project_new_blob_path(@project.namespace, @project, @id),
+ notice: edit_in_new_fork_notice,
+ notice_now: edit_in_new_fork_notice_now }
+ - fork_path = namespace_project_fork_path(@project.namespace, @project, namespace_key: current_user.namespace.id,
+ continue: continue_params)
+ = link_to fork_path, method: :post do
+ = icon('pencil fw')
+ New file
+ %li
+ - continue_params = { to: request.fullpath,
+ notice: edit_in_new_fork_notice + " Try to upload a file again.",
+ notice_now: edit_in_new_fork_notice_now }
+ - fork_path = namespace_project_fork_path(@project.namespace, @project, namespace_key: current_user.namespace.id,
+ continue: continue_params)
+ = link_to fork_path, method: :post do
+ = icon('file fw')
+ Upload file
+ %li
+ - continue_params = { to: request.fullpath,
+ notice: edit_in_new_fork_notice + " Try to create a new directory again.",
+ notice_now: edit_in_new_fork_notice_now }
+ - fork_path = namespace_project_fork_path(@project.namespace, @project, namespace_key: current_user.namespace.id,
+ continue: continue_params)
+ = link_to fork_path, method: :post do
+ = icon('folder fw')
+ New directory
+
+ %li.divider
+ %li
+ = link_to new_namespace_project_branch_path(@project.namespace, @project) do
+ = icon('code-fork fw')
+ New branch
+ %li
+ = link_to new_namespace_project_tag_path(@project.namespace, @project) do
+ = icon('tags fw')
+ New tag
diff --git a/app/views/search/results/_issue.html.haml b/app/views/search/results/_issue.html.haml
index ce8ddff9556..45d700781f3 100644
--- a/app/views/search/results/_issue.html.haml
+++ b/app/views/search/results/_issue.html.haml
@@ -6,7 +6,7 @@
- if issue.description.present?
.description.term
= preserve do
- = search_md_sanitize(markdown(issue.description))
+ = search_md_sanitize(markdown(issue.description, { project: issue.project }))
%span.light
#{issue.project.name_with_namespace}
- if issue.closed?
diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml
index edb5778f424..687a59c270f 100644
--- a/app/views/shared/_clone_panel.html.haml
+++ b/app/views/shared/_clone_panel.html.haml
@@ -1,10 +1,27 @@
- project = project || @project
-.git-clone-holder.input-group
- .input-group-addon.git-protocols
- .input-group-btn
- = ssh_clone_button(project)
- .input-group-btn
- = http_clone_button(project)
+
+.git-clone-holder
+ .btn-group.clone-options
+ %a#clone-dropdown.clone-dropdown-btn.btn{href: '#', 'data-toggle' => 'dropdown'}
+ %span
+ = default_clone_protocol.upcase
+ = icon('angle-down')
+ %ul.dropdown-menu.dropdown-menu-right.clone-options-dropdown
+ %li
+ %a#ssh-selector{href: @project.ssh_url_to_repo}
+ SSH
+ %li
+ %a#http-selector{href: @project.http_url_to_repo}
+ HTTPS
+
= text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true
.input-group-btn
= clipboard_button(clipboard_target: '#project_clone')
+
+:javascript
+ $('ul.clone-options-dropdown a').on('click',function(e){
+ e.preventDefault();
+ var $this = $(this);
+ $('a.clone-dropdown-btn span').text($this.text());
+ $('#project_clone').val($this.attr('href'));
+ });
diff --git a/app/views/shared/_new_commit_form.html.haml b/app/views/shared/_new_commit_form.html.haml
index 111219f2064..0c8ac48bb58 100644
--- a/app/views/shared/_new_commit_form.html.haml
+++ b/app/views/shared/_new_commit_form.html.haml
@@ -1,16 +1,22 @@
= render 'shared/commit_message_container', placeholder: placeholder
-- unless @project.empty_repo?
- .form-group.branch
- = label_tag 'new_branch', 'Target branch', class: 'control-label'
- .col-sm-10
- = text_field_tag 'new_branch', @new_branch || tree_edit_branch, required: true, class: "form-control js-new-branch"
+- if @project.empty_repo?
+ = hidden_field_tag 'target_branch', @ref
+- else
+ - if can?(current_user, :push_code, @project)
+ .form-group.branch
+ = label_tag 'target_branch', 'Target branch', class: 'control-label'
+ .col-sm-10
+ = text_field_tag 'target_branch', @target_branch || tree_edit_branch, required: true, class: "form-control js-target-branch"
- .js-create-merge-request-container
- .checkbox
- - nonce = SecureRandom.hex
- = label_tag "create_merge_request-#{nonce}" do
- = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: "create_merge_request-#{nonce}"
- Start a <strong>new merge request</strong> with these changes
+ .js-create-merge-request-container
+ .checkbox
+ - nonce = SecureRandom.hex
+ = label_tag "create_merge_request-#{nonce}" do
+ = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: "create_merge_request-#{nonce}"
+ Start a <strong>new merge request</strong> with these changes
+ - else
+ = hidden_field_tag 'target_branch', @target_branch || tree_edit_branch
+ = hidden_field_tag 'create_merge_request', 1
= hidden_field_tag 'original_branch', @ref, class: 'js-original-branch'
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index ac6c248ccf1..be06738eac9 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -30,13 +30,13 @@
class: "check_all_issues left"
.issues-other-filters
.filter-item.inline
- = users_select_tag(:assignee_id, selected: params[:assignee_id],
- placeholder: 'Assignee', class: 'trigger-submit', any_user: "Any Assignee", null_user: true, first_user: true, current_user: true)
-
- .filter-item.inline
= users_select_tag(:author_id, selected: params[:author_id],
placeholder: 'Author', class: 'trigger-submit', any_user: "Any Author", first_user: true, current_user: true)
+ .filter-item.inline
+ = users_select_tag(:assignee_id, selected: params[:assignee_id],
+ placeholder: 'Assignee', class: 'trigger-submit', any_user: "Any Assignee", null_user: true, first_user: true, current_user: true)
+
.filter-item.inline.milestone-filter
= select_tag('milestone_title', projects_milestones_options,
class: 'select2 trigger-submit', include_blank: true,
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 91ccd1ef660..90dc0062481 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -19,7 +19,7 @@
- else
Start the title with <code>[WIP]</code> or <code>WIP:</code> to prevent a
<strong>Work In Progress</strong> merge request from being merged before it's ready.
-.form-group.issuable-description
+.form-group.detail-page-description
= f.label :description, 'Description', class: 'control-label'
.col-sm-10
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 0019f739b89..79c5cc7f40a 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -1,13 +1,5 @@
.issuable-sidebar.issuable-affix
= form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f|
- .block
- .title
- Cross-project reference
- .cross-project-reference
- %span#cross-project-reference
- = cross_project_reference(@project, issuable)
- = clipboard_button(clipboard_target: 'span#cross-project-reference')
-
.block.assignee
.title
%label
@@ -62,6 +54,14 @@
= f.collection_select :label_ids, issuable.project.labels.all, :id, :name,
{ selected: issuable.label_ids }, multiple: true, class: 'select2 js-select2', data: { placeholder: "Select labels" }
+ .block
+ .title
+ Cross-project reference
+ .cross-project-reference
+ %span#cross-project-reference
+ = cross_project_reference(@project, issuable)
+ = clipboard_button(clipboard_target: 'span#cross-project-reference')
+
= render "shared/issuable/participants", participants: issuable.participants(current_user)
- if current_user
diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml
index 669e6119fb6..aa5acee9c14 100644
--- a/app/views/shared/snippets/_header.html.haml
+++ b/app/views/shared/snippets/_header.html.haml
@@ -1,25 +1,25 @@
-.issuable-details
- .page-title
- .snippet-box.has_tooltip{class: visibility_level_color(@snippet.visibility_level), title: snippet_visibility_level_description(@snippet.visibility_level, @snippet), data: { container: 'body' }}
- = visibility_level_icon(@snippet.visibility_level, fw: false)
- = visibility_level_label(@snippet.visibility_level)
+.detail-page-header
+ .snippet-box.has_tooltip{class: visibility_level_color(@snippet.visibility_level), title: snippet_visibility_level_description(@snippet.visibility_level, @snippet), data: { container: 'body' }}
+ = visibility_level_icon(@snippet.visibility_level, fw: false)
+ = visibility_level_label(@snippet.visibility_level)
+ %span.identifier
Snippet ##{@snippet.id}
- %span.creator
- &middot; created by #{link_to_member(@project, @snippet.author, size: 24)}
- &middot;
- = time_ago_with_tooltip(@snippet.created_at, placement: 'bottom', html_class: 'snippet_updated_ago')
- - if @snippet.updated_at != @snippet.created_at
- %span
- &middot;
- = icon('edit', title: 'edited')
- = time_ago_with_tooltip(@snippet.updated_at, placement: 'bottom', html_class: 'snippet_edited_ago')
+ %span.creator
+ &middot; created by #{link_to_member(@project, @snippet.author, size: 24)}
+ &middot;
+ = time_ago_with_tooltip(@snippet.created_at, placement: 'bottom', html_class: 'snippet_updated_ago')
+ - if @snippet.updated_at != @snippet.created_at
+ %span
+ &middot;
+ = icon('edit', title: 'edited')
+ = time_ago_with_tooltip(@snippet.updated_at, placement: 'bottom', html_class: 'snippet_edited_ago')
- .pull-right
- - if @snippet.project_id?
- = render "projects/snippets/actions"
- - else
- = render "snippets/actions"
+ .pull-right
+ - if @snippet.project_id?
+ = render "projects/snippets/actions"
+ - else
+ = render "snippets/actions"
- .gray-content-block.middle-block
- %h2.issue-title
- = markdown escape_once(@snippet.title), pipeline: :single_line
+.detail-page-description.gray-content-block.second-block
+ %h2.title
+ = markdown escape_once(@snippet.title), pipeline: :single_line
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index a0a6e2d9810..0bca8177e14 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -1,5 +1,6 @@
-- page_title @user.name
-- header_title @user.name, user_path(@user)
+- page_title @user.name
+- page_description @user.bio
+- header_title @user.name, user_path(@user)
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, user_url(@user, format: :atom), title: "#{@user.name} activity")
@@ -73,7 +74,7 @@
.user-calendar-activities
-%ul.center-top-menu.no-top.no-bottom.bottom-border
+%ul.center-top-menu.no-top.no-bottom.bottom-border.wide
%li.active
= link_to "#activity", 'data-toggle' => 'tab' do
Activity
diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml
index 6071f1484c6..ce0a0113403 100644
--- a/app/views/votes/_votes_block.html.haml
+++ b/app/views/votes/_votes_block.html.haml
@@ -1,33 +1,46 @@
.awards.votes-block
- - votable.notes.awards.grouped_awards.each do |emoji, notes|
+ - awards_sort(votable.notes.awards.grouped_awards).each do |emoji, notes|
.award{class: (note_active_class(notes, current_user)), title: emoji_author_list(notes, current_user)}
- .icon{"data-emoji" => "#{emoji}"}
- = image_tag url_to_emoji(emoji), height: "20px", width: "20px"
+ = emoji_icon(emoji)
.counter
= notes.count
- if current_user
- .dropdown.awards-controls
+ .awards-controls
%a.add-award{"data-toggle" => "dropdown", "data-target" => "#", "href" => "#"}
= icon('smile-o')
- %ul.dropdown-menu.awards-menu
- - emoji_list.each do |emoji|
- %li{"data-emoji" => "#{emoji}"}= image_tag url_to_emoji(emoji), height: "20px", width: "20px"
+ .emoji-menu
+ .emoji-menu-content
+ = text_field_tag :emoji_search, "", class: "emoji-search search-input form-control"
+ - AwardEmoji.emoji_by_category.each do |category, emojis|
+ %h5= AwardEmoji::CATEGORIES[category]
+ %ul
+ - emojis.each do |emoji|
+ %li
+ = emoji_icon(emoji["name"], emoji["unicode"], emoji["aliases"])
- if current_user
:coffeescript
post_emoji_url = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}"
noteable_type = "#{votable.class.name.underscore}"
noteable_id = "#{votable.id}"
- aliases = #{AwardEmoji::ALIASES.to_json}
- window.awards_handler = new AwardsHandler(post_emoji_url, noteable_type, noteable_id, aliases)
+ aliases = #{AwardEmoji.aliases.to_json}
- $(".awards-menu li").click (e)->
- emoji = $(this).data("emoji")
+ window.awards_handler = new AwardsHandler(
+ post_emoji_url,
+ noteable_type,
+ noteable_id,
+ aliases
+ )
+
+ $(".awards").on "click", ".emoji-menu-content li", (e) ->
+ emoji = $(this).find(".emoji-icon").data("emoji")
awards_handler.addAward(emoji)
- $(".awards").on "click", ".award", (e)->
+ $(".awards").on "click", ".award", (e) ->
emoji = $(this).find(".icon").data("emoji")
awards_handler.addAward(emoji)
$(".award").tooltip()
+
+ $(".emoji-menu-content").niceScroll({cursorwidth: "7px", autohidemode: false})
diff --git a/config/database.yml.env b/config/database.yml.env
new file mode 100644
index 00000000000..b2ff23cb5ab
--- /dev/null
+++ b/config/database.yml.env
@@ -0,0 +1,9 @@
+<%= ENV['RAILS_ENV'] %>:
+ adapter: <%= ENV['GITLAB_DATABASE_ADAPTER'] || 'postgresql' %>
+ encoding: <%= ENV['GITLAB_DATABASE_ENCODING'] || 'unicode' %>
+ database: <%= ENV['GITLAB_DATABASE_DATABASE'] || "gitlab_#{ENV['RAILS_ENV']}" %>
+ pool: <%= ENV['GITLAB_DATABASE_POOL'] || '10' %>
+ username: <%= ENV['GITLAB_DATABASE_USERNAME'] || 'root' %>
+ password: <%= ENV['GITLAB_DATABASE_PASSWORD'] || '' %>
+ host: <%= ENV['GITLAB_DATABASE_HOST'] || 'localhost' %>
+ port: <%= ENV['GITLAB_DATABASE_PORT'] || '5432' %>
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index db378118f85..2d9f730c183 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -4,8 +4,8 @@
#
########################### NOTE #####################################
# This file should not receive new settings. All configuration options #
-# that do not require an application restart are being moved to #
-# ApplicationSetting model! #
+# * are being moved to ApplicationSetting model! #
+# If a setting requires an application restart say so in that screen. #
# If you change this file in a Merge Request, please also create #
# a MR on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests #
########################################################################
@@ -144,6 +144,15 @@ production: &base
# plain_url: "http://..." # default: http://www.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon
# ssl_url: "https://..." # default: https://secure.gravatar.com/avatar/%{hash}?s=%{size}&d=identicon
+ ## Auxiliary jobs
+ # Periodically executed jobs, to self-heal Gitlab, do external synchronizations, etc.
+ # Please read here for more information: https://github.com/ondrejbartas/sidekiq-cron#adding-cron-job
+ cron_jobs:
+ # Flag stuck CI builds as failed
+ stuck_ci_builds_worker:
+ cron: "0 0 * * *"
+
+
#
# 2. GitLab CI settings
# ==========================
@@ -287,6 +296,15 @@ production: &base
# arguments, followed by optional 'args' which can be either a hash or an array.
# Documentation for this is available at http://doc.gitlab.com/ce/integration/omniauth.html
providers:
+ # See omniauth-cas3 for more configuration details
+ # - { name: 'cas3',
+ # label: 'cas3',
+ # args: {
+ # url: 'https://sso.example.com',
+ # disable_ssl_verification: false,
+ # login_url: '/cas/login',
+ # service_validate_url: '/cas/p3/serviceValidate',
+ # logout_url: '/cas/logout'} }
# - { name: 'github',
# app_id: 'YOUR_APP_ID',
# app_secret: 'YOUR_APP_SECRET',
@@ -324,6 +342,10 @@ production: &base
# application_name: 'YOUR_APP_NAME',
# application_password: 'YOUR_APP_PASSWORD' } }
+ # SSO maximum session duration in seconds. Defaults to CAS default of 8 hours.
+ # cas3:
+ # session_duration: 28800
+
# Shared file storage settings
shared:
# path: /mnt/gitlab # Default: shared
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 63d8ae17436..4fbd84ee890 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -126,6 +126,11 @@ Settings.omniauth['block_auto_created_users'] = true if Settings.omniauth['block
Settings.omniauth['auto_link_ldap_user'] = false if Settings.omniauth['auto_link_ldap_user'].nil?
Settings.omniauth['providers'] ||= []
+Settings.omniauth['cas3'] ||= Settingslogic.new({})
+Settings.omniauth.cas3['session_duration'] ||= 8.hours
+Settings.omniauth['session_tickets'] ||= Settingslogic.new({})
+Settings.omniauth.session_tickets['cas3'] = 'ticket'
+
Settings['shared'] ||= Settingslogic.new({})
Settings.shared['path'] = File.expand_path(Settings.shared['path'] || "shared", Rails.root)
@@ -140,16 +145,16 @@ Settings.gitlab['default_projects_limit'] ||= 10
Settings.gitlab['default_branch_protection'] ||= 2
Settings.gitlab['default_can_create_group'] = true if Settings.gitlab['default_can_create_group'].nil?
Settings.gitlab['default_theme'] = Gitlab::Themes::APPLICATION_DEFAULT if Settings.gitlab['default_theme'].nil?
-Settings.gitlab['host'] ||= 'localhost'
+Settings.gitlab['host'] ||= ENV['GITLAB_HOST'] || 'localhost'
Settings.gitlab['ssh_host'] ||= Settings.gitlab.host
Settings.gitlab['https'] = false if Settings.gitlab['https'].nil?
Settings.gitlab['port'] ||= Settings.gitlab.https ? 443 : 80
Settings.gitlab['relative_url_root'] ||= ENV['RAILS_RELATIVE_URL_ROOT'] || ''
Settings.gitlab['protocol'] ||= Settings.gitlab.https ? "https" : "http"
Settings.gitlab['email_enabled'] ||= true if Settings.gitlab['email_enabled'].nil?
-Settings.gitlab['email_from'] ||= "gitlab@#{Settings.gitlab.host}"
-Settings.gitlab['email_display_name'] ||= "GitLab"
-Settings.gitlab['email_reply_to'] ||= "noreply@#{Settings.gitlab.host}"
+Settings.gitlab['email_from'] ||= ENV['GITLAB_EMAIL_FROM'] || "gitlab@#{Settings.gitlab.host}"
+Settings.gitlab['email_display_name'] ||= ENV['GITLAB_EMAIL_DISPLAY_NAME'] || 'GitLab'
+Settings.gitlab['email_reply_to'] ||= ENV['GITLAB_EMAIL_REPLY_TO'] || "noreply@#{Settings.gitlab.host}"
Settings.gitlab['base_url'] ||= Settings.send(:build_base_gitlab_url)
Settings.gitlab['url'] ||= Settings.send(:build_gitlab_url)
Settings.gitlab['user'] ||= 'git'
@@ -164,7 +169,7 @@ Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].
Settings.gitlab['twitter_sharing_enabled'] ||= true if Settings.gitlab['twitter_sharing_enabled'].nil?
Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], [])
Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil?
-Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?))+)' if Settings.gitlab['issue_closing_pattern'].nil?
+Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?)|([A-Z]*-\d*))+)' if Settings.gitlab['issue_closing_pattern'].nil?
Settings.gitlab['default_projects_features'] ||= {}
Settings.gitlab['webhook_timeout'] ||= 10
Settings.gitlab['max_attachment_size'] ||= 10
@@ -225,6 +230,15 @@ Settings.gravatar['ssl_url'] ||= 'https://secure.gravatar.com/avatar/%{hash}?
Settings.gravatar['host'] = Settings.get_host_without_www(Settings.gravatar['plain_url'])
#
+# Cron Jobs
+#
+Settings['cron_jobs'] ||= Settingslogic.new({})
+Settings.cron_jobs['stuck_ci_builds_worker'] ||= Settingslogic.new({})
+Settings.cron_jobs['stuck_ci_builds_worker']['cron'] ||= '0 0 * * *'
+Settings.cron_jobs['stuck_ci_builds_worker']['job_class'] = 'StuckCiBuildsWorker'
+
+
+#
# GitLab Shell
#
Settings['gitlab_shell'] ||= Settingslogic.new({})
diff --git a/config/initializers/carrierwave.rb b/config/initializers/carrierwave.rb
index bfb8656df55..df28d30d750 100644
--- a/config/initializers/carrierwave.rb
+++ b/config/initializers/carrierwave.rb
@@ -31,11 +31,11 @@ if File.exists?(aws_file)
if Rails.env.test?
Fog.mock!
connection = ::Fog::Storage.new(
- aws_access_key_id: AWS_CONFIG['access_key_id'],
- aws_secret_access_key: AWS_CONFIG['secret_access_key'],
- provider: 'AWS',
- region: AWS_CONFIG['region']
- )
+ aws_access_key_id: AWS_CONFIG['access_key_id'],
+ aws_secret_access_key: AWS_CONFIG['secret_access_key'],
+ provider: 'AWS',
+ region: AWS_CONFIG['region']
+ )
connection.directories.create(key: AWS_CONFIG['bucket'])
end
end
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index 5fb43a86e13..d82cfb3ec0c 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -121,14 +121,14 @@ Devise.setup do |config|
config.lock_strategy = :failed_attempts
# Defines which key will be used when locking and unlocking an account
- # config.unlock_keys = [ :email ]
+ config.unlock_keys = [ :email ]
# Defines which strategy will be used to unlock an account.
# :email = Sends an unlock link to the user email
# :time = Re-enables login after a certain amount of time (see :unlock_in below)
# :both = Enables both strategies
# :none = No unlock strategy. You should handle unlocking by yourself.
- config.unlock_strategy = :time
+ config.unlock_strategy = :both
# Number of authentication tries before locking an account if lock_strategy
# is failed attempts.
@@ -241,6 +241,16 @@ Devise.setup do |config|
# An Array from the configuration will be expanded.
provider_arguments.concat provider['args']
when Hash
+ # Add procs for handling SLO
+ if provider['name'] == 'cas3'
+ provider['args'][:on_single_sign_out] = lambda do |request|
+ ticket = request.params[:session_index]
+ raise "Service Ticket not found." unless Gitlab::OAuth::Session.valid?(:cas3, ticket)
+ Gitlab::OAuth::Session.destroy(:cas3, ticket)
+ true
+ end
+ end
+
# A Hash from the configuration will be passed as is.
provider_arguments << provider['args'].symbolize_keys
end
diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb
new file mode 100644
index 00000000000..2e4908192a1
--- /dev/null
+++ b/config/initializers/metrics.rb
@@ -0,0 +1,64 @@
+if Gitlab::Metrics.enabled?
+ require 'influxdb'
+ require 'socket'
+ require 'connection_pool'
+ require 'method_source'
+
+ # These are manually require'd so the classes are registered properly with
+ # ActiveSupport.
+ require 'gitlab/metrics/subscribers/action_view'
+ require 'gitlab/metrics/subscribers/active_record'
+
+ Gitlab::Application.configure do |config|
+ config.middleware.use(Gitlab::Metrics::RackMiddleware)
+ end
+
+ Sidekiq.configure_server do |config|
+ config.server_middleware do |chain|
+ chain.add Gitlab::Metrics::SidekiqMiddleware
+ end
+ end
+
+ # This instruments all methods residing in app/models that (appear to) use any
+ # of the ActiveRecord methods. This has to take place _after_ initializing as
+ # for some unknown reason calling eager_load! earlier breaks Devise.
+ Gitlab::Application.config.after_initialize do
+ Rails.application.eager_load!
+
+ models = Rails.root.join('app', 'models').to_s
+
+ regex = Regexp.union(
+ ActiveRecord::Querying.public_instance_methods(false).map(&:to_s)
+ )
+
+ Gitlab::Metrics::Instrumentation.
+ instrument_class_hierarchy(ActiveRecord::Base) do |klass, method|
+ # Instrumenting the ApplicationSetting class can lead to an infinite
+ # loop. Since the data is cached any way we don't really need to
+ # instrument it.
+ if klass == ApplicationSetting
+ false
+ else
+ loc = method.source_location
+
+ loc && loc[0].start_with?(models) && method.source =~ regex
+ end
+ end
+ end
+
+ Gitlab::Metrics::Instrumentation.configure do |config|
+ config.instrument_instance_methods(Gitlab::Shell)
+
+ config.instrument_methods(Gitlab::Git)
+
+ Gitlab::Git.constants.each do |name|
+ const = Gitlab::Git.const_get(name)
+
+ config.instrument_methods(const) if const.is_a?(Module)
+ end
+ end
+
+ GC::Profiler.enable
+
+ Gitlab::Metrics::Sampler.new.start
+end
diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index 2e3a71912ef..dcf6ce74d96 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -18,11 +18,12 @@ Sidekiq.configure_server do |config|
chain.add Gitlab::SidekiqMiddleware::MemoryKiller if ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS']
end
- # Sidekiq-cron: load recurring jobs from schedule.yml
- schedule_file = 'config/schedule.yml'
- if File.exists?(schedule_file)
- Sidekiq::Cron::Job.load_from_hash YAML.load_file(schedule_file)
- end
+ # Sidekiq-cron: load recurring jobs from gitlab.yml
+ # 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') }
+ Sidekiq::Cron::Job.load_from_hash! cron_jobs
# Database pool should be at least `sidekiq_concurrency` + 2
# For more info, see: https://github.com/mperham/sidekiq/blob/master/4.0-Upgrade.md
diff --git a/config/routes.rb b/config/routes.rb
index e2d4fcb65a8..3e7d9f78710 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -188,7 +188,7 @@ Rails.application.routes.draw do
namespace :admin do
resources :users, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } do
resources :keys, only: [:show, :destroy]
- resources :identities, only: [:index, :edit, :update, :destroy]
+ resources :identities, except: [:show]
delete 'stop_impersonation' => 'impersonation#destroy', on: :collection
@@ -297,6 +297,7 @@ Rails.application.routes.draw do
resource :two_factor_auth, only: [:new, :create, :destroy] do
member do
post :codes
+ patch :skip
end
end
end
@@ -441,7 +442,7 @@ Rails.application.routes.draw do
scope do
post(
- '/create_dir/*id',
+ '/create_dir/*id',
to: 'tree#create_dir',
constraints: { id: /.+/ },
as: 'create_dir'
diff --git a/config/schedule.rb b/config/schedule.rb
deleted file mode 100644
index 8122f7cc69c..00000000000
--- a/config/schedule.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# Use this file to easily define all of your cron jobs.
-#
-# If you make changes to this file, please create also an issue on
-# https://gitlab.com/gitlab-org/omnibus-gitlab/issues . This is necessary
-# because the omnibus packages manage cron jobs using Chef instead of Whenever.
-every 1.hour do
- rake "ci:schedule_builds"
-end
diff --git a/config/schedule.yml b/config/schedule.yml
deleted file mode 100644
index 993a95fef56..00000000000
--- a/config/schedule.yml
+++ /dev/null
@@ -1,10 +0,0 @@
-# Here is a list of jobs that are scheduled to run periodically.
-# We use a UNIX cron notation to specify execution schedule.
-#
-# Please read here for more information:
-# https://github.com/ondrejbartas/sidekiq-cron#adding-cron-job
-
-stuck_ci_builds_worker:
- cron: "0 0 * * *"
- class: "StuckCiBuildsWorker"
- queue: "default"
diff --git a/db/migrate/20151012173029_set_jira_service_api_url.rb b/db/migrate/20151012173029_set_jira_service_api_url.rb
new file mode 100644
index 00000000000..2af99e0db0b
--- /dev/null
+++ b/db/migrate/20151012173029_set_jira_service_api_url.rb
@@ -0,0 +1,50 @@
+class SetJiraServiceApiUrl < ActiveRecord::Migration
+ # This migration can be performed online without errors, but some Jira API calls may be missed
+ # when doing so because api_url is not yet available.
+
+ def build_api_url_from_project_url(project_url, api_version)
+ # this is the exact logic previously used to build the Jira API URL from project_url
+ server = URI(project_url)
+ default_ports = [80, 443].include?(server.port)
+ server_url = "#{server.scheme}://#{server.host}"
+ server_url.concat(":#{server.port}") unless default_ports
+ "#{server_url}/rest/api/#{api_version}"
+ end
+
+ def get_api_version_from_api_url(api_url)
+ match = /\/rest\/api\/(?<api_version>\w+)$/.match(api_url)
+ match && match['api_version']
+ end
+
+ def change
+ reversible do |dir|
+ select_all("SELECT id, properties FROM services WHERE services.type IN ('JiraService')").each do |jira_service|
+ id = jira_service["id"]
+ properties = JSON.parse(jira_service["properties"])
+ properties_was = properties.clone
+
+ dir.up do
+ # remove api_version and set api_url
+ if properties['api_version'].present? && properties['project_url'].present?
+ begin
+ properties['api_url'] ||= build_api_url_from_project_url(properties['project_url'], properties['api_version'])
+ rescue
+ # looks like project_url was not a valid URL. Do nothing.
+ end
+ end
+ properties.delete('api_version') if properties.include?('api_version')
+ end
+
+ dir.down do
+ # remove api_url and set api_version (default to '2')
+ properties['api_version'] ||= get_api_version_from_api_url(properties['api_url']) || '2'
+ properties.delete('api_url') if properties.include?('api_url')
+ end
+
+ if properties != properties_was
+ execute("UPDATE services SET properties = '#{quote_string(properties.to_json)}' WHERE id = #{id}")
+ end
+ end
+ end
+ end
+end
diff --git a/db/migrate/20151203162134_add_build_events_to_services.rb b/db/migrate/20151203162134_add_build_events_to_services.rb
index a84be7db3f1..c5542cb864d 100644
--- a/db/migrate/20151203162134_add_build_events_to_services.rb
+++ b/db/migrate/20151203162134_add_build_events_to_services.rb
@@ -1,5 +1,5 @@
class AddBuildEventsToServices < ActiveRecord::Migration
- def up
+ def change
add_column :services, :build_events, :boolean, default: false, null: false
add_column :web_hooks, :build_events, :boolean, default: false, null: false
end
diff --git a/db/migrate/20151209144329_migrate_ci_web_hooks.rb b/db/migrate/20151209144329_migrate_ci_web_hooks.rb
index 825ba1973ff..d7e196e6763 100644
--- a/db/migrate/20151209144329_migrate_ci_web_hooks.rb
+++ b/db/migrate/20151209144329_migrate_ci_web_hooks.rb
@@ -10,4 +10,7 @@ class MigrateCiWebHooks < ActiveRecord::Migration
'JOIN projects ON ci_projects.gitlab_id = projects.id'
)
end
+
+ def down
+ end
end
diff --git a/db/migrate/20151210030143_add_unlock_token_to_user.rb b/db/migrate/20151210030143_add_unlock_token_to_user.rb
new file mode 100644
index 00000000000..0ea66ba65df
--- /dev/null
+++ b/db/migrate/20151210030143_add_unlock_token_to_user.rb
@@ -0,0 +1,5 @@
+class AddUnlockTokenToUser < ActiveRecord::Migration
+ def change
+ add_column :users, :unlock_token, :string
+ end
+end
diff --git a/db/migrate/20151210125928_add_ci_to_project.rb b/db/migrate/20151210125928_add_ci_to_project.rb
index 8a65abab636..8c167f64a2b 100644
--- a/db/migrate/20151210125928_add_ci_to_project.rb
+++ b/db/migrate/20151210125928_add_ci_to_project.rb
@@ -1,5 +1,5 @@
class AddCiToProject < ActiveRecord::Migration
- def up
+ def change
add_column :projects, :ci_id, :integer
add_column :projects, :builds_enabled, :boolean, default: true, null: false
add_column :projects, :shared_runners_enabled, :boolean, default: true, null: false
diff --git a/db/migrate/20151210125929_add_project_id_to_ci.rb b/db/migrate/20151210125929_add_project_id_to_ci.rb
index 5d1cf543576..84273591fa2 100644
--- a/db/migrate/20151210125929_add_project_id_to_ci.rb
+++ b/db/migrate/20151210125929_add_project_id_to_ci.rb
@@ -1,5 +1,5 @@
class AddProjectIdToCi < ActiveRecord::Migration
- def up
+ def change
add_column :ci_builds, :gl_project_id, :integer
add_column :ci_runner_projects, :gl_project_id, :integer
add_column :ci_triggers, :gl_project_id, :integer
diff --git a/db/migrate/20151210125930_migrate_ci_to_project.rb b/db/migrate/20151210125930_migrate_ci_to_project.rb
index 7dfe05174ee..c32c7feb193 100644
--- a/db/migrate/20151210125930_migrate_ci_to_project.rb
+++ b/db/migrate/20151210125930_migrate_ci_to_project.rb
@@ -14,6 +14,10 @@ class MigrateCiToProject < ActiveRecord::Migration
migrate_ci_service
end
+ def down
+ # We can't reverse the data
+ end
+
def migrate_project_id_for_table(table)
subquery = "SELECT gitlab_id FROM ci_projects WHERE ci_projects.id = #{table}.project_id"
execute("UPDATE #{table} SET gl_project_id=(#{subquery}) WHERE gl_project_id IS NULL")
@@ -26,7 +30,8 @@ class MigrateCiToProject < ActiveRecord::Migration
def migrate_project_column(column, new_column = nil)
new_column ||= column
- subquery = "SELECT ci_projects.#{column} FROM ci_projects WHERE projects.id = ci_projects.gitlab_id"
+ subquery = "SELECT ci_projects.#{column} FROM ci_projects WHERE projects.id = ci_projects.gitlab_id " \
+ 'ORDER BY ci_projects.updated_at DESC LIMIT 1'
execute("UPDATE projects SET #{new_column}=(#{subquery}) WHERE (#{subquery}) IS NOT NULL")
end
diff --git a/db/migrate/20151210125931_add_index_to_ci_tables.rb b/db/migrate/20151210125931_add_index_to_ci_tables.rb
index 9fedb5d612c..5e129c9303d 100644
--- a/db/migrate/20151210125931_add_index_to_ci_tables.rb
+++ b/db/migrate/20151210125931_add_index_to_ci_tables.rb
@@ -1,5 +1,5 @@
class AddIndexToCiTables < ActiveRecord::Migration
- def up
+ def change
add_index :ci_builds, :gl_project_id
add_index :ci_runner_projects, :gl_project_id
add_index :ci_triggers, :gl_project_id
diff --git a/db/migrate/20151210125932_drop_null_for_ci_tables.rb b/db/migrate/20151210125932_drop_null_for_ci_tables.rb
index 0b007430b0c..c520c2ed56f 100644
--- a/db/migrate/20151210125932_drop_null_for_ci_tables.rb
+++ b/db/migrate/20151210125932_drop_null_for_ci_tables.rb
@@ -1,5 +1,5 @@
class DropNullForCiTables < ActiveRecord::Migration
- def up
+ def change
remove_index :ci_variables, :project_id
remove_index :ci_runner_projects, :project_id
change_column_null :ci_triggers, :project_id, true
diff --git a/db/migrate/20151218154042_add_tfa_to_application_settings.rb b/db/migrate/20151218154042_add_tfa_to_application_settings.rb
new file mode 100644
index 00000000000..dd95db775c5
--- /dev/null
+++ b/db/migrate/20151218154042_add_tfa_to_application_settings.rb
@@ -0,0 +1,8 @@
+class AddTfaToApplicationSettings < ActiveRecord::Migration
+ def change
+ change_table :application_settings do |t|
+ t.boolean :require_two_factor_authentication, default: false
+ t.integer :two_factor_grace_period, default: 48
+ end
+ end
+end
diff --git a/db/migrate/20151221234414_add_tfa_additional_fields.rb b/db/migrate/20151221234414_add_tfa_additional_fields.rb
new file mode 100644
index 00000000000..c16df47932f
--- /dev/null
+++ b/db/migrate/20151221234414_add_tfa_additional_fields.rb
@@ -0,0 +1,7 @@
+class AddTfaAdditionalFields < ActiveRecord::Migration
+ def change
+ change_table :users do |t|
+ t.datetime :otp_grace_period_started_at, null: true
+ end
+ end
+end
diff --git a/db/migrate/20151224123230_rename_emojis.rb b/db/migrate/20151224123230_rename_emojis.rb
new file mode 100644
index 00000000000..62d921dfdcc
--- /dev/null
+++ b/db/migrate/20151224123230_rename_emojis.rb
@@ -0,0 +1,15 @@
+# Migration type: online without errors (works on previous version and new one)
+class RenameEmojis < ActiveRecord::Migration
+ def up
+ # Renames aliases to main names
+ execute("UPDATE notes SET note ='thumbsup' WHERE is_award = true AND note = '+1'")
+ execute("UPDATE notes SET note ='thumbsdown' WHERE is_award = true AND note = '-1'")
+ execute("UPDATE notes SET note ='poop' WHERE is_award = true AND note = 'shit'")
+ end
+
+ def down
+ execute("UPDATE notes SET note ='+1' WHERE is_award = true AND note = 'thumbsup'")
+ execute("UPDATE notes SET note ='-1' WHERE is_award = true AND note = 'thumbsdown'")
+ execute("UPDATE notes SET note ='shit' WHERE is_award = true AND note = 'poop'")
+ end
+end
diff --git a/db/migrate/20151228150906_influxdb_settings.rb b/db/migrate/20151228150906_influxdb_settings.rb
new file mode 100644
index 00000000000..3012bd52cfd
--- /dev/null
+++ b/db/migrate/20151228150906_influxdb_settings.rb
@@ -0,0 +1,18 @@
+class InfluxdbSettings < ActiveRecord::Migration
+ def change
+ add_column :application_settings, :metrics_enabled, :boolean, default: false
+
+ add_column :application_settings, :metrics_host, :string,
+ default: 'localhost'
+
+ add_column :application_settings, :metrics_database, :string,
+ default: 'gitlab'
+
+ add_column :application_settings, :metrics_username, :string
+ add_column :application_settings, :metrics_password, :string
+ add_column :application_settings, :metrics_pool_size, :integer, default: 16
+ add_column :application_settings, :metrics_timeout, :integer, default: 10
+ add_column :application_settings, :metrics_method_call_threshold,
+ :integer, default: 10
+ end
+end
diff --git a/db/migrate/20151228175719_add_recaptcha_to_application_settings.rb b/db/migrate/20151228175719_add_recaptcha_to_application_settings.rb
new file mode 100644
index 00000000000..259fd0248d2
--- /dev/null
+++ b/db/migrate/20151228175719_add_recaptcha_to_application_settings.rb
@@ -0,0 +1,9 @@
+class AddRecaptchaToApplicationSettings < ActiveRecord::Migration
+ def change
+ change_table :application_settings do |t|
+ t.boolean :recaptcha_enabled, default: false
+ t.string :recaptcha_site_key
+ t.string :recaptcha_private_key
+ end
+ end
+end
diff --git a/db/migrate/20151229102248_influxdb_udp_port_setting.rb b/db/migrate/20151229102248_influxdb_udp_port_setting.rb
new file mode 100644
index 00000000000..ae0499f936d
--- /dev/null
+++ b/db/migrate/20151229102248_influxdb_udp_port_setting.rb
@@ -0,0 +1,5 @@
+class InfluxdbUdpPortSetting < ActiveRecord::Migration
+ def change
+ add_column :application_settings, :metrics_port, :integer, default: 8089
+ end
+end
diff --git a/db/migrate/20151229112614_influxdb_remote_database_setting.rb b/db/migrate/20151229112614_influxdb_remote_database_setting.rb
new file mode 100644
index 00000000000..f0e1ee1e7a7
--- /dev/null
+++ b/db/migrate/20151229112614_influxdb_remote_database_setting.rb
@@ -0,0 +1,5 @@
+class InfluxdbRemoteDatabaseSetting < ActiveRecord::Migration
+ def change
+ remove_column :application_settings, :metrics_database
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 0167e30ff8b..df7f72d5ad4 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: 20151210125932) do
+ActiveRecord::Schema.define(version: 20151229112614) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -33,23 +33,36 @@ ActiveRecord::Schema.define(version: 20151210125932) do
t.datetime "created_at"
t.datetime "updated_at"
t.string "home_page_url"
- t.integer "default_branch_protection", default: 2
- t.boolean "twitter_sharing_enabled", default: true
+ t.integer "default_branch_protection", default: 2
+ t.boolean "twitter_sharing_enabled", default: true
t.text "restricted_visibility_levels"
- t.boolean "version_check_enabled", default: true
- t.integer "max_attachment_size", default: 10, null: false
+ t.boolean "version_check_enabled", default: true
+ t.integer "max_attachment_size", default: 10, null: false
t.integer "default_project_visibility"
t.integer "default_snippet_visibility"
t.text "restricted_signup_domains"
- t.boolean "user_oauth_applications", default: true
+ t.boolean "user_oauth_applications", default: true
t.string "after_sign_out_path"
- t.integer "session_expire_delay", default: 10080, null: false
+ t.integer "session_expire_delay", default: 10080, null: false
t.text "import_sources"
t.text "help_page_text"
t.string "admin_notification_email"
- t.boolean "shared_runners_enabled", default: true, null: false
- t.integer "max_artifacts_size", default: 100, null: false
+ t.boolean "shared_runners_enabled", default: true, null: false
+ t.integer "max_artifacts_size", default: 100, null: false
t.string "runners_registration_token"
+ t.boolean "require_two_factor_authentication", default: false
+ t.integer "two_factor_grace_period", default: 48
+ t.boolean "metrics_enabled", default: false
+ t.string "metrics_host", default: "localhost"
+ t.string "metrics_username"
+ t.string "metrics_password"
+ t.integer "metrics_pool_size", default: 16
+ t.integer "metrics_timeout", default: 10
+ t.integer "metrics_method_call_threshold", default: 10
+ t.boolean "recaptcha_enabled", default: false
+ t.string "recaptcha_site_key"
+ t.string "recaptcha_private_key"
+ t.integer "metrics_port", default: 8089
end
create_table "audit_events", force: :cascade do |t|
@@ -783,12 +796,12 @@ ActiveRecord::Schema.define(version: 20151210125932) do
add_index "tags", ["name"], name: "index_tags_on_name", unique: true, using: :btree
create_table "users", force: :cascade do |t|
- t.string "email", default: "", null: false
- t.string "encrypted_password", default: "", null: false
+ t.string "email", default: "", null: false
+ t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
- t.integer "sign_in_count", default: 0
+ t.integer "sign_in_count", default: 0
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
@@ -796,22 +809,22 @@ ActiveRecord::Schema.define(version: 20151210125932) do
t.datetime "created_at"
t.datetime "updated_at"
t.string "name"
- t.boolean "admin", default: false, null: false
- t.integer "projects_limit", default: 10
- t.string "skype", default: "", null: false
- t.string "linkedin", default: "", null: false
- t.string "twitter", default: "", null: false
+ t.boolean "admin", default: false, null: false
+ t.integer "projects_limit", default: 10
+ t.string "skype", default: "", null: false
+ t.string "linkedin", default: "", null: false
+ t.string "twitter", default: "", null: false
t.string "authentication_token"
- t.integer "theme_id", default: 1, null: false
+ t.integer "theme_id", default: 1, null: false
t.string "bio"
- t.integer "failed_attempts", default: 0
+ t.integer "failed_attempts", default: 0
t.datetime "locked_at"
t.string "username"
- t.boolean "can_create_group", default: true, null: false
- t.boolean "can_create_team", default: true, null: false
+ t.boolean "can_create_group", default: true, null: false
+ t.boolean "can_create_team", default: true, null: false
t.string "state"
- t.integer "color_scheme_id", default: 1, null: false
- t.integer "notification_level", default: 1, null: false
+ t.integer "color_scheme_id", default: 1, null: false
+ t.integer "notification_level", default: 1, null: false
t.datetime "password_expires_at"
t.integer "created_by_id"
t.datetime "last_credential_check_at"
@@ -820,23 +833,25 @@ ActiveRecord::Schema.define(version: 20151210125932) do
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
t.string "unconfirmed_email"
- t.boolean "hide_no_ssh_key", default: false
- t.string "website_url", default: "", null: false
+ t.boolean "hide_no_ssh_key", default: false
+ t.string "website_url", default: "", null: false
t.string "notification_email"
- t.boolean "hide_no_password", default: false
- t.boolean "password_automatically_set", default: false
+ t.boolean "hide_no_password", default: false
+ t.boolean "password_automatically_set", default: false
t.string "location"
t.string "encrypted_otp_secret"
t.string "encrypted_otp_secret_iv"
t.string "encrypted_otp_secret_salt"
- t.boolean "otp_required_for_login", default: false, null: false
+ t.boolean "otp_required_for_login", default: false, null: false
t.text "otp_backup_codes"
- t.string "public_email", default: "", null: false
- t.integer "dashboard", default: 0
- t.integer "project_view", default: 0
+ t.string "public_email", default: "", null: false
+ t.integer "dashboard", default: 0
+ t.integer "project_view", default: 0
t.integer "consumed_timestep"
- t.integer "layout", default: 0
- t.boolean "hide_project_limit", default: false
+ t.integer "layout", default: 0
+ t.boolean "hide_project_limit", default: false
+ t.string "unlock_token"
+ t.datetime "otp_grace_period_started_at"
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
diff --git a/doc/README.md b/doc/README.md
index a3098094210..8a297f8267f 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -7,6 +7,7 @@
- [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).
- [Markdown](markdown/markdown.md) GitLab's advanced formatting system.
+- [Migrating from SVN](migration/README.md) Convert a SVN repository to Git and GitLab
- [Permissions](permissions/permissions.md) Learn what each role in a project (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.
@@ -27,17 +28,18 @@
- [Using SSH keys](ci/ssh_keys/README.md)
- [User permissions](ci/permissions/README.md)
- [API](ci/api/README.md)
+- [Triggering builds through the API](ci/triggers/README.md)
### CI Languages
-+ [Testing PHP](ci/languages/php.md)
+- [Testing PHP](ci/languages/php.md)
### CI Services
-+ [Using MySQL](ci/services/mysql.md)
-+ [Using PostgreSQL](ci/services/postgres.md)
-+ [Using Redis](ci/services/redis.md)
-+ [Using Other Services](ci/docker/using_docker_images.md#how-to-use-other-images-as-services)
+- [Using MySQL](ci/services/mysql.md)
+- [Using PostgreSQL](ci/services/postgres.md)
+- [Using Redis](ci/services/redis.md)
+- [Using Other Services](ci/docker/using_docker_images.md#how-to-use-other-images-as-services)
### CI Examples
@@ -54,6 +56,7 @@
- [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](logs/logs.md) Log system.
+- [Environmental Variables](administration/environment_variables.md) to configure GitLab.
- [Operations](operations/README.md) Keeping GitLab up and running
- [Raketasks](raketasks/README.md) Backups, maintenance, automatic web hook setup and the importing of projects.
- [Security](security/README.md) Learn what you can do to further secure your GitLab instance.
diff --git a/doc/administration/enviroment_variables.md b/doc/administration/enviroment_variables.md
new file mode 100644
index 00000000000..7d8f9d29eef
--- /dev/null
+++ b/doc/administration/enviroment_variables.md
@@ -0,0 +1,48 @@
+# Environment Variables
+
+## Introduction
+
+Commonly people configure GitLab via the gitlab.rb configuration file in the Omnibus package.
+
+But if you prefer to use environment variables we allow that too.
+
+## Supported environment variables
+
+Variable | Type | Explanation
+-------- | ---- | -----------
+GITLAB_ROOT_PASSWORD | string | sets the password for the `root` user on installation
+GITLAB_HOST | url | hostname of the GitLab server includes http or https
+RAILS_ENV | production / development / staging / test | Rails environment
+DATABASE_URL | url | For example: postgresql://localhost/blog_development?pool=5
+GITLAB_EMAIL_FROM | email | Email address used in the "From" field in mails sent by GitLab
+GITLAB_EMAIL_DISPLAY_NAME | string | Name used in the "From" field in mails sent by GitLab
+GITLAB_EMAIL_REPLY_TO | email | Email address used in the "Reply-To" field in mails sent by GitLab
+
+## Complete database variables
+
+As explained in the [Heroku documentation](https://devcenter.heroku.com/articles/rails-database-connection-behavior) the DATABASE_URL doesn't let you set:
+
+- adapter
+- database
+- username
+- password
+- host
+- port
+
+To do so please `cp config/database.yml.env config/database.yml` and use the following variables:
+
+Variable | Default
+--- | ---
+GITLAB_DATABASE_ADAPTER | postgresql
+GITLAB_DATABASE_ENCODING | unicode
+GITLAB_DATABASE_DATABASE | gitlab_#{ENV['RAILS_ENV']
+GITLAB_DATABASE_POOL | 10
+GITLAB_DATABASE_USERNAME | root
+GITLAB_DATABASE_PASSWORD |
+GITLAB_DATABASE_HOST | localhost
+GITLAB_DATABASE_PORT | 5432
+
+## Other variables
+
+We welcome merge requests to make more settings configurable via variables.
+Please stick to the naming scheme "GITLAB_#{name 1_settings.rb in upper case}".
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 366a1f8abec..8bc0a67067a 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -4,8 +4,7 @@
Get all merge requests for this project.
The `state` parameter can be used to get only merge requests with a given state (`opened`, `closed`, or `merged`) or all of them (`all`).
-The pagination parameters `page` and `per_page` can be used to restrict the list of merge requests. With GitLab 8.2 the return fields `upvotes` and
-`downvotes` are deprecated and always return `0`.
+The pagination parameters `page` and `per_page` can be used to restrict the list of merge requests.
```
GET /projects/:id/merge_requests
@@ -58,7 +57,7 @@ Parameters:
## Get single MR
-Shows information about a single merge request. With GitLab 8.2 the return fields `upvotes` and `downvotes` are deprecated and always return `0`.
+Shows information about a single merge request.
```
GET /projects/:id/merge_request/:merge_request_id
@@ -141,8 +140,6 @@ Parameters:
## Get single MR changes
Shows information about the merge request including its files and changes.
-With GitLab 8.2 the return fields `upvotes` and `downvotes` are deprecated and
-always return `0`.
```
GET /projects/:id/merge_request/:merge_request_id/changes
@@ -213,9 +210,7 @@ Parameters:
## Create MR
-Creates a new merge request. With GitLab 8.2 the return fields `upvotes` and `
-downvotes` are deprecated and always return `0`.
-
+Creates a new merge request.
```
POST /projects/:id/merge_requests
```
@@ -266,8 +261,7 @@ If an error occurs, an error number and a message explaining the reason is retur
## Update MR
-Updates an existing merge request. You can change the target branch, title, or even close the MR. With GitLab 8.2 the return fields `upvotes` and `downvotes`
-are deprecated and always return `0`.
+Updates an existing merge request. You can change the target branch, title, or even close the MR.
```
PUT /projects/:id/merge_request/:merge_request_id
@@ -318,8 +312,7 @@ If an error occurs, an error number and a message explaining the reason is retur
## Accept MR
-Merge changes submitted with MR using this API. With GitLab 8.2 the return
-fields `upvotes` and `downvotes` are deprecated and always return `0`.
+Merge changes submitted with MR using this API.
If merge success you get `200 OK`.
diff --git a/doc/api/notes.md b/doc/api/notes.md
index 4d7ef288df8..d4d63e825ab 100644
--- a/doc/api/notes.md
+++ b/doc/api/notes.md
@@ -6,8 +6,7 @@ Notes are comments on snippets, issues or merge requests.
### List project issue notes
-Gets a list of all notes for a single issue. With GitLab 8.2 the return fields
-`upvote` and `downvote` are deprecated and always return `false`.
+Gets a list of all notes for a single issue.
```
GET /projects/:id/issues/:issue_id/notes
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 1a524400627..0ca81ffd49e 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -58,6 +58,7 @@ Parameters:
"path": "diaspora-client",
"path_with_namespace": "diaspora/diaspora-client",
"issues_enabled": true,
+ "open_issues_count": 1,
"merge_requests_enabled": true,
"builds_enabled": true,
"wiki_enabled": true,
@@ -100,6 +101,7 @@ Parameters:
"path": "puppet",
"path_with_namespace": "brightbox/puppet",
"issues_enabled": true,
+ "open_issues_count": 1,
"merge_requests_enabled": true,
"builds_enabled": true,
"wiki_enabled": true,
@@ -116,6 +118,16 @@ Parameters:
"path": "brightbox",
"updated_at": "2013-09-30T13:46:02Z"
},
+ "permissions": {
+ "project_access": {
+ "access_level": 10,
+ "notification_level": 3
+ },
+ "group_access": {
+ "access_level": 50,
+ "notification_level": 3
+ }
+ },
"archived": false,
"avatar_url": null
}
@@ -137,6 +149,21 @@ Parameters:
- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
- `search` (optional) - Return list of authorized projects according to a search criteria
+### List starred projects
+
+Get a list of projects which are starred by the authenticated user.
+
+```
+GET /projects/starred
+```
+
+Parameters:
+
+- `archived` (optional) - if passed, limit by archived status
+- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at`
+- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc`
+- `search` (optional) - Return list of authorized projects according to a search criteria
+
### List ALL projects
Get a list of all GitLab projects (admin only).
@@ -189,6 +216,7 @@ Parameters:
"path": "diaspora-project-site",
"path_with_namespace": "diaspora/diaspora-project-site",
"issues_enabled": true,
+ "open_issues_count": 1,
"merge_requests_enabled": true,
"builds_enabled": true,
"wiki_enabled": true,
diff --git a/doc/api/users.md b/doc/api/users.md
index 7ba2db248ff..66d2fd52526 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -90,7 +90,17 @@ GET /users
You can search for users by email or username with: `/users?search=John`
-Also see `def search query` in `app/models/user.rb`.
+In addition, you can lookup users by username:
+
+```
+GET /users?username=:username
+```
+
+For example:
+
+```
+GET /users?username=jack_smith
+```
## Single user
diff --git a/doc/ci/README.md b/doc/ci/README.md
index 5d9d7a81db3..a1f5513d88e 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -2,28 +2,30 @@
### User documentation
-+ [Quick Start](quick_start/README.md)
-+ [Configuring project (.gitlab-ci.yml)](yaml/README.md)
-+ [Configuring runner](runners/README.md)
-+ [Configuring deployment](deployment/README.md)
-+ [Using Docker Images](docker/using_docker_images.md)
-+ [Using Docker Build](docker/using_docker_build.md)
-+ [Using Variables](variables/README.md)
-+ [Using SSH keys](ssh_keys/README.md)
+* [Quick Start](quick_start/README.md)
+* [Configuring project (.gitlab-ci.yml)](yaml/README.md)
+* [Configuring runner](runners/README.md)
+* [Configuring deployment](deployment/README.md)
+* [Using Docker Images](docker/using_docker_images.md)
+* [Using Docker Build](docker/using_docker_build.md)
+* [Using Variables](variables/README.md)
+* [Using SSH keys](ssh_keys/README.md)
+* [Triggering builds through the API](triggers/README.md)
### Languages
-+ [Testing PHP](languages/php.md)
+* [Testing PHP](languages/php.md)
### Services
-+ [Using MySQL](services/mysql.md)
-+ [Using PostgreSQL](services/postgres.md)
-+ [Using Redis](services/redis.md)
-+ [Using Other Services](docker/using_docker_images.md#how-to-use-other-images-as-services)
+* [Using MySQL](services/mysql.md)
+* [Using PostgreSQL](services/postgres.md)
+* [Using Redis](services/redis.md)
+* [Using Other Services](docker/using_docker_images.md#how-to-use-other-images-as-services)
### Examples
++ [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml)
+ [Test and deploy Ruby applications to Heroku](examples/test-and-deploy-ruby-application-to-heroku.md)
+ [Test and deploy Python applications to Heroku](examples/test-and-deploy-python-application-to-heroku.md)
+ [Test Clojure applications](examples/test-clojure-application.md)
@@ -31,5 +33,5 @@
### Administrator documentation
-+ [User permissions](permissions/README.md)
-+ [API](api/README.md)
+* [User permissions](permissions/README.md)
+* [API](api/README.md)
diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md
index 8d4bd44053e..31458d61674 100644
--- a/doc/ci/docker/using_docker_images.md
+++ b/doc/ci/docker/using_docker_images.md
@@ -1,11 +1,11 @@
# Using Docker Images
-GitLab CI in conjuction with [GitLab Runner](../runners/README.md) can use
+GitLab CI in conjunction with [GitLab Runner](../runners/README.md) can use
[Docker Engine](https://www.docker.com/) to test and build any application.
Docker is an open-source project that allows you to use predefined images to
run applications in independent "containers" that are run within a single Linux
-instance. [Docker Hub][hub] has a rich database of prebuilt images that can be
+instance. [Docker Hub][hub] has a rich database of pre-built images that can be
used to test and build your applications.
Docker, when used with GitLab CI, runs each build in a separate and isolated
@@ -136,6 +136,24 @@ Look for the `[runners.docker]` section:
The image and services defined this way will be added to all builds run by
that runner.
+## Define an image from a private Docker registry
+
+Starting with GitLab Runner 0.6.0, you are able to define images located to
+private registries that could also require authentication.
+
+All you have to do is be explicit on the image definition in `.gitlab-ci.yml`.
+
+```yaml
+image: my.registry.tld:5000/namepace/image:tag
+```
+
+In the example above, GitLab Runner will look at `my.registry.tld:5000` for the
+image `namespace/image:tag`.
+
+If the repository is private you need to authenticate your GitLab Runner in the
+registry. Learn how to do that on
+[GitLab Runner's documentation][runner-priv-reg].
+
## Accessing the services
Let's say that you need a Wordpress instance to test some API integration with
@@ -258,3 +276,4 @@ creation.
[tutum/wordpress]: https://registry.hub.docker.com/u/tutum/wordpress/
[postgres-hub]: https://registry.hub.docker.com/u/library/postgres/
[mysql-hub]: https://registry.hub.docker.com/u/library/mysql/
+[runner-priv-reg]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/blob/master/docs/configuration/advanced-configuration.md#using-a-private-docker-registry
diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md
new file mode 100644
index 00000000000..9f7c1bfe6a0
--- /dev/null
+++ b/doc/ci/triggers/README.md
@@ -0,0 +1,172 @@
+# Triggering Builds through the API
+
+_**Note:** This feature was [introduced][ci-229] in GitLab CE 7.14_
+
+Triggers can be used to force a rebuild of a specific branch, tag or commit,
+with an API call.
+
+## Add a trigger
+
+You can add a new trigger by going to your project's **Settings > Triggers**.
+The **Add trigger** button will create a new token which you can then use to
+trigger a rebuild of this particular project.
+
+Every new trigger you create, gets assigned a different token which you can
+then use inside your scripts or `.gitlab-ci.yml`. You also have a nice
+overview of the time the triggers were last used.
+
+![Triggers page overview](img/triggers_page.png)
+
+## Revoke a trigger
+
+You can revoke a trigger any time by going at your project's
+**Settings > Triggers** and hitting the **Revoke** button. The action is
+irreversible.
+
+## Trigger a build
+
+To trigger a build you need to send a `POST` request to GitLab's API endpoint:
+
+```
+POST /projects/:id/trigger/builds
+```
+
+The required parameters are the trigger's `token` and the Git `ref` on which
+the trigger will be performed. Valid refs are the branch, the tag or the commit
+SHA. The `:id` of a project can be found by [querying the API](../api/projects.md)
+or by visiting the **Triggers** page which provides self-explanatory examples.
+
+When a rebuild is triggered, the information is exposed in GitLab's UI under
+the **Builds** page and the builds are marked as `triggered`.
+
+![Marked rebuilds as triggered on builds page](img/builds_page.png)
+
+---
+
+You can see which trigger caused the rebuild by visiting the single build page.
+The token of the trigger is exposed in the UI as you can see from the image
+below.
+
+![Marked rebuilds as triggered on a single build page](img/trigger_single_build.png)
+
+---
+
+See the [Examples](#examples) section for more details on how to actually
+trigger a rebuild.
+
+## Pass build variables to a trigger
+
+You can pass any number of arbitrary variables in the trigger API call and they
+will be available in GitLab CI so that they can be used in your `.gitlab-ci.yml`
+file. The parameter is of the form:
+
+```
+variables[key]=value
+```
+
+This information is also exposed in the UI.
+
+![Build variables in UI](img/trigger_variables.png)
+
+---
+
+See the [Examples](#examples) section below for more details.
+
+## Examples
+
+Using cURL you can trigger a rebuild with minimal effort, for example:
+
+```bash
+curl -X POST \
+ -F token=TOKEN \
+ -F ref=master \
+ https://gitlab.example.com/api/v3/projects/9/trigger/builds
+```
+
+In this case, the project with ID `9` will get rebuilt on `master` branch.
+
+
+### Triggering a build within `.gitlab-ci.yml`
+
+You can also benefit by using triggers in your `.gitlab-ci.yml`. Let's say that
+you have two projects, A and B, and you want to trigger a rebuild on the `master`
+branch of project B whenever a tag on project A is created. This is the job you
+need to add in project's A `.gitlab-ci.yml`:
+
+```yaml
+build_docs:
+ stage: deploy
+ script:
+ - "curl -X POST -F token=TOKEN -F ref=master https://gitlab.example.com/api/v3/projects/9/trigger/builds"
+ only:
+ - tags
+```
+
+Now, whenever a new tag is pushed on project A, the build will run and the
+`build_docs` job will be executed, triggering a rebuild of project B. The
+`stage: deploy` ensures that this job will run only after all jobs with
+`stage: test` complete successfully.
+
+_**Note:** If your project is public, passing the token in plain text is
+probably not the wisest idea, so you might want to use a
+[secure variable](../variables/README.md#user-defined-variables-secure-variables)
+for that purpose._
+
+### Making use of trigger variables
+
+Using trigger variables can be proven useful for a variety of reasons.
+
+* Identifiable jobs. Since the variable is exposed in the UI you can know
+ why the rebuild was triggered if you pass a variable that explains the
+ purpose.
+* Conditional job processing. You can have conditional jobs that run whenever
+ a certain variable is present.
+
+Consider the following `.gitlab-ci.yml` where we set three
+[stages](../yaml/README.md#stages) and the `upload_package` job is run only
+when all jobs from the test and build stages pass. When the `UPLOAD_TO_S3`
+variable is non-zero, `make upload` is run.
+
+```yaml
+stages:
+- test
+- build
+- package
+
+run_tests:
+ script:
+ - make test
+
+build_package:
+ stage: build
+ script:
+ - make build
+
+upload_package:
+ stage: package
+ script:
+ - if [ -n "${UPLOAD_TO_S3}" ]; then make upload; fi
+```
+
+You can then trigger a rebuild while you pass the `UPLOAD_TO_S3` variable
+and the script of the `upload_package` job will run:
+
+```bash
+curl -X POST \
+ -F token=TOKEN \
+ -F ref=master \
+ -F "variables[UPLOAD_TO_S3]=true" \
+ https://gitlab.example.com/api/v3/projects/9/trigger/builds
+```
+
+### Using cron to trigger nightly builds
+
+Whether you craft a script or just run cURL directly, you can trigger builds
+in conjunction with cron. The example below triggers a build on the `master`
+branch of project with ID `9` every night at `00:30`:
+
+```bash
+30 0 * * * curl -X POST -F token=TOKEN -F ref=master https://gitlab.example.com/api/v3/projects/9/trigger/builds
+```
+
+[ci-229]: https://gitlab.com/gitlab-org/gitlab-ci/merge_requests/229
diff --git a/doc/ci/triggers/img/builds_page.png b/doc/ci/triggers/img/builds_page.png
new file mode 100644
index 00000000000..e78794fbee7
--- /dev/null
+++ 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
new file mode 100644
index 00000000000..c25f27409d6
--- /dev/null
+++ 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
new file mode 100644
index 00000000000..2207e8b34cb
--- /dev/null
+++ 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
new file mode 100644
index 00000000000..268368dc3c5
--- /dev/null
+++ b/doc/ci/triggers/img/triggers_page.png
Binary files differ
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 022afb70042..b99ea25a3fe 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -27,7 +27,6 @@ The API_TOKEN will take the Secure Variable value: `SECURE`.
| **CI_BUILD_TAG** | 0.5 | The commit tag name. Present only when building tags. |
| **CI_BUILD_NAME** | 0.5 | The name of the build as defined in `.gitlab-ci.yml` |
| **CI_BUILD_STAGE** | 0.5 | The name of the stage as defined in `.gitlab-ci.yml` |
-| **CI_BUILD_BEFORE_SHA** | all | The first commit that were included in push request |
| **CI_BUILD_REF_NAME** | all | The branch or tag name for which project is built |
| **CI_BUILD_ID** | all | The unique id of the current build that GitLab CI uses internally |
| **CI_BUILD_REPO** | all | The URL to clone the Git repository |
@@ -40,7 +39,6 @@ The API_TOKEN will take the Secure Variable value: `SECURE`.
Example values:
```bash
-export CI_BUILD_BEFORE_SHA="9df57456fa9de2a6d335ca5edf9750ed812b9df0"
export CI_BUILD_ID="50"
export CI_BUILD_REF="1ecfd275763eff1d6b4844ea3168962458c9f27a"
export CI_BUILD_REF_NAME="master"
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 7e2edb945da..fd0d49de4e4 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -1,9 +1,12 @@
# Configuration of your builds with .gitlab-ci.yml
-From version 7.12, GitLab CI uses a [YAML](https://en.wikipedia.org/wiki/YAML) file (**.gitlab-ci.yml**) for the project configuration.
-It is placed in the root 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:
+From version 7.12, GitLab CI uses a [YAML](https://en.wikipedia.org/wiki/YAML)
+file (`.gitlab-ci.yml`) for the project configuration. It is placed in the root
+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:
```yaml
job1:
@@ -13,15 +16,21 @@ job2:
script: "execute-script-for-job2"
```
-The above example is the simplest possible CI configuration with two separate jobs,
-where each of the jobs executes a different command.
-Of course a command can execute code directly (`./configure;make;make install`) or run a script (`test.sh`) in the repository.
+The above example is the simplest possible CI configuration with two separate
+jobs, where each of the jobs executes a different command.
+
+Of course a command can execute code directly (`./configure;make;make install`)
+or run a script (`test.sh`) in the repository.
-Jobs are used to create builds, which are then picked up by [runners](../runners/README.md) and executed within the environment of the runner.
-What is important, is that each job is run independently from each other.
+Jobs are used to create builds, which are then picked up by
+[runners](../runners/README.md) and executed within the environment of the
+runner. What is important, is that each job is run independently from each
+other.
## .gitlab-ci.yml
-The YAML syntax allows for using more complex job specifications than in the above example:
+
+The YAML syntax allows for using more complex job specifications than in the
+above example:
```yaml
image: ruby:2.1
@@ -46,26 +55,31 @@ job1:
- docker
```
-There are a few `keywords` that can't be used as job names:
+There are a few reserved `keywords` that **cannot** be used as job names:
-| keyword | required | description |
+| Keyword | Required | Description |
|---------------|----------|-------------|
-| image | optional | Use docker image, covered in [Use Docker](../docker/README.md) |
-| services | optional | Use docker services, covered in [Use Docker](../docker/README.md) |
-| stages | optional | Define build stages |
-| types | optional | Alias for `stages` |
-| before_script | optional | Define commands prepended for each job's script |
-| variables | optional | Define build variables |
-| cache | optional | Define list of files that should be cached between subsequent runs |
+| image | no | Use docker image, covered in [Use Docker](../docker/README.md) |
+| services | no | Use docker services, covered in [Use Docker](../docker/README.md) |
+| stages | no | Define build stages |
+| types | no | Alias for `stages` |
+| before_script | no | Define commands that run before each job's script |
+| variables | no | Define build variables |
+| cache | no | Define list of files that should be cached between subsequent runs |
### image and services
-This allows to specify a custom Docker image and a list of services that can be used for time of the build.
-The configuration of this feature is covered in separate document: [Use Docker](../docker/README.md).
+
+This allows to specify a custom Docker image and a list of services that can be
+used for time of the build. The configuration of this feature is covered in
+separate document: [Use Docker](../docker/README.md).
### before_script
-`before_script` is used to define the command that should be run before all builds, including deploy builds. This can be an array or a multiline string.
+
+`before_script` is used to define the command that should be run before all
+builds, including deploy builds. This can be an array or a multi-line string.
### stages
+
`stages` is used to define build stages that can be used by jobs.
The specification of `stages` allows for having flexible multi stage pipelines.
@@ -75,7 +89,8 @@ The ordering of elements in `stages` defines the ordering of builds' execution:
1. Builds of next stage are run after success.
Let's consider the following example, which defines 3 stages:
-```
+
+```yaml
stages:
- build
- test
@@ -86,21 +101,26 @@ stages:
1. If all jobs of `build` succeeds, the `test` jobs are executed in parallel.
1. If all jobs of `test` succeeds, the `deploy` jobs are executed in parallel.
1. If all jobs of `deploy` succeeds, the commit is marked as `success`.
-1. If any of the previous jobs fails, the commit is marked as `failed` and no jobs of further stage are executed.
+1. If any of the previous jobs fails, the commit is marked as `failed` and no
+ jobs of further stage are executed.
There are also two edge cases worth mentioning:
-1. If no `stages` is defined in `.gitlab-ci.yml`, then by default the `build`, `test` and `deploy` are allowed to be used as job's stage by default.
+1. If no `stages` is 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.
### types
+
Alias for [stages](#stages).
### variables
-**This feature requires `gitlab-runner` with version equal or greater than 0.5.0.**
-GitLab CI allows you to add to `.gitlab-ci.yml` variables that are set in build environment.
-The variables are stored in repository and are meant to store non-sensitive project configuration, ie. RAILS_ENV or DATABASE_URL.
+_**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:
```yaml
variables:
@@ -109,18 +129,23 @@ variables:
These variables can be later used in all executed commands and scripts.
-The YAML-defined variables are also set to all created service containers, thus allowing to fine tune them.
+The YAML-defined variables are also set to all created service containers,
+thus allowing to fine tune them.
### cache
-`cache` is used to specify list of files and directories which should be cached between builds.
-Caches are stored according to the branch/ref and the job name. Caches are not
-currently shared between different job names or between branches/refs. This means
-caching will benefit you if you push subsequent commits to an existing feature branch.
-**The global setting allows to specify default cached files for all jobs.**
+`cache` is used to specify a list of files and directories which should be
+cached between builds. Caches are stored according to the branch/ref and the
+job name. They are not currently shared between different job names or between
+branches/refs, which means that caching will benefit you if you push subsequent
+commits to an existing feature branch.
+
+If `cache` is defined outside the scope of the jobs, it means it is set
+globally and all jobs will use its definition.
To cache all git untracked files and files in `binaries`:
-```
+
+```yaml
cache:
untracked: true
paths:
@@ -128,9 +153,10 @@ cache:
```
## Jobs
-`.gitlab-ci.yml` allows you to specify an unlimited number of jobs.
-Each job has to have a unique `job_name`, which is not one of the keywords mentioned above.
-A job is defined by a list of parameters that define the build behaviour.
+
+`.gitlab-ci.yml` allows you to specify an unlimited number of jobs. Each job
+must have a unique name, which is not one of the Keywords mentioned above.
+A job is defined by a list of parameters that define the build behavior.
```yaml
job_name:
@@ -148,21 +174,22 @@ job_name:
allow_failure: true
```
-| keyword | required | description |
+| Keyword | Required | Description |
|---------------|----------|-------------|
-| script | required | Defines a shell script which is executed by runner |
-| stage | optional (default: test) | Defines a build stage |
-| type | optional | Alias for `stage` |
-| only | optional | Defines a list of git refs for which build is created |
-| except | optional | Defines a list of git refs for which build is not created |
-| tags | optional | Defines a list of tags which are used to select runner |
-| allow_failure | optional | Allow build to fail. Failed build doesn't contribute to commit status |
-| when | optional | Define when to run build. Can be `on_success`, `on_failure` or `always` |
-| artifacts | optional | Define list build artifacts |
-| cache | optional | Define list of files that should be cached between subsequent runs |
+| script | yes | Defines a shell script which is executed by runner |
+| stage | no (default: `test`) | Defines a build stage |
+| type | no | Alias for `stage` |
+| only | no | Defines a list of git refs for which build is created |
+| except | no | Defines a list of git refs for which build is not created |
+| tags | no | Defines a list of tags which are used to select runner |
+| allow_failure | no | Allow build to fail. Failed build doesn't contribute to commit status |
+| when | no | Define when to run build. Can be `on_success`, `on_failure` or `always` |
+| artifacts | no | Define list build artifacts |
+| cache | no | Define list of files that should be cached between subsequent runs |
### script
-`script` is a shell script which is executed by runner. The shell script is prepended with `before_script`.
+
+`script` is a shell script which is executed by the runner. For example:
```yaml
job:
@@ -170,6 +197,7 @@ job:
```
This parameter can also contain several commands using an array:
+
```yaml
job:
script:
@@ -178,31 +206,45 @@ job:
```
### stage
-`stage` allows to group build into different stages. Builds of the same `stage` are executed in `parallel`.
-For more info about the use of `stage` please check the [stages](#stages).
+
+`stage` allows to group build into different stages. Builds of the same `stage`
+are executed in `parallel`. For more info about the use of `stage` please check
+[stages](#stages).
### only and except
-This are two parameters that allow for setting a refs policy to limit when jobs are built:
-1. `only` defines the names of branches and tags for which job will be built.
-2. `except` defines the names of branches and tags for which the job wil **not** be built.
-There are a few rules that apply to usage of refs policy:
+`only` and `except` are two parameters that set a refs policy to limit when
+jobs are built:
-1. `only` and `except` are inclusive. If both `only` and `except` are defined in job specification the ref is filtered by `only` and `except`.
-1. `only` and `except` allow for using the regexp expressions.
-1. `only` and `except` allow for using special keywords: `branches` and `tags`.
-These names can be used for example to exclude all tags and all branches.
+1. `only` defines the names of branches and tags for which the job will be
+ built.
+2. `except` defines the names of branches and tags for which the job will
+ **not** be built.
+
+There are a few rules that apply to the usage of refs policy:
+
+* `only` and `except` are inclusive. If both `only` and `except` are defined
+ in a job specification, the ref is filtered by `only` and `except`.
+* `only` and `except` allow the use of regular expressions.
+* `only` and `except` allow the use of special keywords: `branches` and `tags`.
+* `only` and `except` allow to specify a repository path to filter jobs for
+ forks.
+
+In the example below, `job` will run only for refs that start with `issue-`,
+whereas all branches will be skipped.
```yaml
job:
+ # use regexp
only:
- - /^issue-.*$/ # use regexp
+ - /^issue-.*$/
+ # use special keyword
except:
- - branches # use special keyword
+ - branches
```
-1. `only` and `except` allow for specify repository path to filter jobs for forks.
-The repository path can be used to have jobs executed only for parent repository.
+The repository path can be used to have jobs executed only for the parent
+repository and not forks:
```yaml
job:
@@ -211,33 +253,47 @@ job:
except:
- master@gitlab-org/gitlab-ce
```
-The above will run `job` for all branches on `gitlab-org/gitlab-ce`, except master .
+
+The above example will run `job` for all branches on `gitlab-org/gitlab-ce`,
+except master.
### tags
-`tags` is used to select specific runners from the list of all runners that are allowed to run this project.
-During registration of a runner, you can specify the runner's tags, ie.: `ruby`, `postgres`, `development`.
-`tags` allow you to run builds with runners that have the specified tags assigned:
+`tags` is used to select specific runners from the list of all runners that are
+allowed to run this project.
-```
+During the registration of a runner, you can specify the runner's tags, for
+example `ruby`, `postgres`, `development`.
+
+`tags` allow you to run builds with runners that have the specified tags
+assigned to them:
+
+```yaml
job:
tags:
- ruby
- postgres
```
-The above specification will make sure that `job` is built by a runner that have `ruby` AND `postgres` tags defined.
+The specification above, will make sure that `job` is built by a runner that
+has both `ruby` AND `postgres` tags defined.
### when
-`when` is used to implement jobs that are run in case of failure or despite the failure.
+
+`when` is used to implement jobs that are run in case of failure or despite the
+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.
-1. `on_failure` - execute build only when at least one build from prior stages failed.
+1. `on_success` - execute build only when all builds from prior stages
+ succeeded. 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.
-```
+For example:
+
+```yaml
stages:
- build
- cleanup_build
@@ -245,28 +301,28 @@ stages:
- deploy
- cleanup
-build:
+build_job:
stage: build
script:
- make build
-cleanup_build:
+cleanup_build_job:
stage: cleanup_build
script:
- cleanup build when failed
when: on_failure
-test:
+test_job:
stage: test
script:
- make test
-deploy:
+deploy_job:
stage: deploy
script:
- make deploy
-cleanup:
+cleanup_job:
stage: cleanup
script:
- cleanup after builds
@@ -274,84 +330,108 @@ cleanup:
```
The above script will:
-1. Execute `cleanup_build` only when the `build` failed,
-2. Always execute `cleanup` as the last step in pipeline.
+
+1. Execute `cleanup_build_job` only when `build_job` fails
+2. Always execute `cleanup_job` as the last step in pipeline.
### artifacts
-`artifacts` is used to specify list of files and directories which should be attached to build after success.
-1. Send all files in `binaries` and `.config`:
+_**Note:** Introduced in GitLab Runner v0.7.0. Also, the Windows shell executor
+ does not currently support artifact uploads._
- artifacts:
- paths:
- - binaries/
- - .config
+`artifacts` is used to specify list of files and directories which should be
+attached to build after success. Below are some examples.
-2. Send all git untracked files:
+Send all files in `binaries` and `.config`:
- artifacts:
- untracked: true
+```yaml
+artifacts:
+ paths:
+ - binaries/
+ - .config
+```
-3. Send all git untracked files and files in `binaries`:
+Send all git untracked files:
- artifacts:
- untracked: true
- paths:
- - binaries/
+```yaml
+artifacts:
+ untracked: true
+```
+
+Send all git untracked files and files in `binaries`:
-The artifacts will be send after the build success to GitLab and will be accessible in GitLab interface to download.
+```yaml
+artifacts:
+ untracked: true
+ paths:
+ - binaries/
+```
-This feature requires GitLab Runner v0.7.0 or higher.
+The artifacts will be send after a successful build success to GitLab, and will
+be accessible in the GitLab UI to download.
### cache
-`cache` is used to specify list of files and directories which should be cached between builds.
-1. Cache all files in `binaries` and `.config`:
+_**Note:** Introduced in GitLab Runner v0.7.0._
- rspec:
- script: test
- cache:
- paths:
- - binaries/
- - .config
+`cache` is used to specify list of files and directories which should be cached
+between builds. Below are some examples:
-2. Cache all git untracked files:
+Cache all files in `binaries` and `.config`:
- rspec:
- script: test
- cache:
- untracked: true
-
-3. Cache all git untracked files and files in `binaries`:
+```yaml
+rspec:
+ script: test
+ cache:
+ paths:
+ - binaries/
+ - .config
+```
- rspec:
- script: test
- cache:
- untracked: true
- paths:
- - binaries/
+Cache all git untracked files:
-4. Locally defined cache overwrites globally defined options. This will cache only `binaries/`:
+```yaml
+rspec:
+ script: test
+ cache:
+ untracked: true
+```
+
+Cache all git untracked files and files in `binaries`:
+
+```yaml
+rspec:
+ script: test
+ cache:
+ untracked: true
+ paths:
+ - binaries/
+```
- cache:
- paths:
- - my/files
-
- rspec:
- script: test
- cache:
- paths:
- - binaries/
+Locally defined cache overwrites globally defined options. This will cache only
+`binaries/`:
-The cache is provided on best effort basis, so don't expect that cache will be present.
-For implementation details please check GitLab Runner.
+```yaml
+cache:
+ paths:
+ - my/files
-This feature requires GitLab Runner v0.7.0 or higher.
+rspec:
+ script: test
+ cache:
+ paths:
+ - 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.
## Validate the .gitlab-ci.yml
+
Each instance of GitLab CI has an embedded debug tool called Lint.
-You can find the link to the Lint in the project's settings page or use short url `/lint`.
+You can find the link under `/ci/lint` of your gitlab instance.
## Skipping builds
-There is one more way to skip all builds, if your commit message contains tag [ci skip]. In this case, commit will be created but builds will be skipped
+
+If your commit message contains `[ci skip]`, the commit will be created but the
+builds will be skipped.
diff --git a/doc/install/installation.md b/doc/install/installation.md
index f8116a8a31c..81edd8da2b8 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -348,7 +348,7 @@ GitLab Shell is an SSH access and repository management software developed speci
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 0.4.2
+ sudo -u git -H git checkout 0.5.1
sudo -u git -H make
### Initialize Database and Activate Advanced Features
diff --git a/doc/integration/README.md b/doc/integration/README.md
index eff39a626ae..2a9f76533b7 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -4,13 +4,16 @@ GitLab integrates with multiple third-party services to allow external issue tra
See the documentation below for details on how to configure these services.
+- [Jira](jira.md) Integrate with the JIRA issue tracker
- [External issue tracker](external-issue-tracker.md) Redmine, JIRA, etc.
- [LDAP](ldap.md) Set up sign in via LDAP
- [OmniAuth](omniauth.md) Sign in via Twitter, GitHub, GitLab, and Google via OAuth.
- [SAML](saml.md) Configure GitLab as a SAML 2.0 Service Provider
+- [CAS](cas.md) Configure GitLab to sign in using CAS
- [Slack](slack.md) Integrate with the Slack chat service
- [OAuth2 provider](oauth_provider.md) OAuth2 application creation
- [Gmail actions buttons](gmail_action_buttons_for_gitlab.md) Adds GitLab actions to messages
+- [reCAPTCHA](recaptcha.md) Configure GitLab to use Google reCAPTCHA for new users
GitLab Enterprise Edition contains [advanced JIRA support](http://doc.gitlab.com/ee/integration/jira.html) and [advanced Jenkins support](http://doc.gitlab.com/ee/integration/jenkins.html).
diff --git a/doc/integration/cas.md b/doc/integration/cas.md
new file mode 100644
index 00000000000..e6b2071f193
--- /dev/null
+++ b/doc/integration/cas.md
@@ -0,0 +1,62 @@
+# CAS OmniAuth Provider
+
+To enable the CAS OmniAuth provider you must register your application with your CAS instance. This requires the service URL GitLab will supply to CAS. It should be something like: `https://gitlab.example.com:443/users/auth/cas3/callback?url`. By default handling for SLO is enabled, you only need to configure CAS for backchannel logout.
+
+1. On your GitLab server, open the configuration file.
+
+ For omnibus package:
+
+ ```sh
+ sudo editor /etc/gitlab/gitlab.rb
+ ```
+
+ For installations from source:
+
+ ```sh
+ cd /home/git/gitlab
+
+ sudo -u git -H editor config/gitlab.yml
+ ```
+
+1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
+
+1. Add the provider configuration:
+
+ For omnibus package:
+
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ {
+ name: "cas3",
+ label: "cas",
+ args: {
+ url: 'CAS_SERVER',
+ login_url: '/CAS_PATH/login',
+ service_validate_url: '/CAS_PATH/p3/serviceValidate',
+ logout_url: '/CAS_PATH/logout'} }
+ }
+ }
+ ]
+ ```
+
+ For installations from source:
+
+ ```
+ - { name: 'cas3',
+ label: 'cas',
+ args: {
+ url: 'CAS_SERVER',
+ login_url: '/CAS_PATH/login',
+ service_validate_url: '/CAS_PATH/p3/serviceValidate',
+ logout_url: '/CAS_PATH/logout'} }
+ ```
+
+1. Change 'CAS_PATH' to the root of your CAS instance (ie. `cas`).
+
+1. If your CAS instance does not use default TGC lifetimes, update the `cas3.session_duration` to at least the current TGC maximum lifetime. To explicitly disable SLO, regardless of CAS settings, set this to 0.
+
+1. Save the configuration file.
+
+1. Restart GitLab for the changes to take effect.
+
+On the sign in page there should now be a CAS tab in the sign in form.
diff --git a/doc/integration/jira.md b/doc/integration/jira.md
new file mode 100644
index 00000000000..624601d0fac
--- /dev/null
+++ b/doc/integration/jira.md
@@ -0,0 +1,113 @@
+# GitLab Jira integration
+
+GitLab can be configured to interact with Jira.
+Configuration happens via username and password.
+Connecting to a Jira server via CAS is not possible.
+
+Each project can be configured to connect to a different Jira instance, configuration is explained [here](#configuration).
+If you have one Jira instance you can pre-fill the settings page with a default template. To configure the template [see external issue tracker document](external-issue-tracker.md#service-template)).
+
+Once the project is connected to Jira, you can reference and close the issues in Jira directly from GitLab.
+
+
+## Table of Contents
+
+* [Referencing Jira Issues from GitLab](#referencing-jira-issues)
+* [Closing Jira Issues from GitLab](#closing-jira-issues)
+* [Configuration](#configuration)
+
+### Referencing Jira Issues
+
+When GitLab project has Jira issue tracker configured and enabled, mentioning Jira issue in GitLab will automatically add a comment in Jira issue with the link back to GitLab. This means that in comments in merge requests and commits referencing an issue, eg. `PROJECT-7`, will add a comment in Jira issue in the format:
+
+
+```
+ USER mentioned this issue in LINK_TO_THE_MENTION
+```
+
+* `USER` A user that mentioned the issue. This is the link to the user profile in GitLab.
+* `LINK_TO_THE_MENTION` Link to the origin of mention with a name of the entity where Jira issue was mentioned.
+Can be commit or merge request.
+
+
+![example of mentioning or closing the Jira issue](jira_issue_reference.png)
+
+
+### Closing Jira Issues
+
+Jira issues can be closed directly from GitLab by using trigger words, eg. `Resolves PROJECT-1`, `Closes PROJECT-1` or `Fixes PROJECT-1`, in commits and merge requests.
+When a commit which contains the trigger word in the commit message is pushed, GitLab will add a comment in the mentioned Jira issue.
+
+For example, for project named PROJECT in Jira, we implemented a new feature and created a merge request in GitLab.
+
+This feature was requested in Jira issue PROJECT-7. Merge request in GitLab contains the improvement and in merge request description we say that this merge request `Closes PROJECT-7` issue.
+
+Once this merge request is merged, Jira issue will be automatically closed with a link to the commit that resolved the issue.
+
+![A Git commit that causes the Jira issue to be closed](merge_request_close_jira.png)
+
+
+![The GitLab integration user leaves a comment on Jira](jira_service_close_issue.png)
+
+
+## Configuration
+
+### Configuring JIRA
+
+We need to create a user in JIRA which will have access to all projects that need to integrate with GitLab.
+Login to your JIRA instance as admin and under Administration go to User Management and create a new user.
+As an example, we'll create a user named `gitlab` and add it to `jira-developers` group.
+
+**It is important that the user `gitlab` has write-access to projects in JIRA**
+
+### Configuring GitLab
+
+### GitLab 7.8 EE and up with JIRA v6.x
+
+To enable JIRA integration in a project, navigate to the project Settings page and go to Services. Here you will find JIRA.
+
+Fill in the required details on the page:
+
+![Jira service page](jira_service_page.png)
+
+* `description` A name for the issue tracker (to differentiate between instances, for instance).
+* `project url` The URL to the JIRA project which is being linked to this GitLab project.
+* `issues url` The URL to the JIRA project issues overview for the project that is linked to this GitLab project.
+* `new issue url` This is the URL to create a new issue in JIRA for the project linked to this GitLab project.
+* `api url` The base URL of the JIRA API. It may be omitted, in which case GitLab will automatically use API version `2` based on the `project url`, i.e. `https://jira.example.com/rest/api/2`.
+* `username` The username of the user created in [configuring JIRA step](#configuring-jira).
+* `password` The password of the user created in [configuring JIRA step](#configuring-jira).
+* `Jira issue transition` This is the id of a transition that moves issues to a closed state. You can find this number under [JIRA workflow administration, see screenshot](jira_workflow_screenshot.png). By default, this id is `2`. (In the example image, this is `2` as well)
+
+After saving the configuration, your GitLab project will be able to interact with the linked JIRA project.
+
+
+### GitLab 6.x-7.7 with JIRA v6.x
+
+**Note: GitLab 7.8 and up contain various integration improvements. We strongly recommend upgrading.**
+
+
+In `gitlab.yml` enable [JIRA issue tracker section by uncommenting the lines](https://gitlab.com/subscribers/gitlab-ee/blob/6-8-stable-ee/config/gitlab.yml.example#L111-115).
+This will make sure that all issues within GitLab are pointing to the JIRA issue tracker.
+
+We can also enable JIRA service that will allow us to interact with JIRA issues.
+
+For example, we can close issues in JIRA by a commit in GitLab.
+
+Go to project settings page and fill in the project name for the JIRA project:
+
+![Set the JIRA project name in GitLab to 'NEW'](jira_project_name.png)
+
+Next, go to the services page and find JIRA.
+
+![Jira services page](jira_service.png)
+
+1. Tick the active check box to enable the service.
+1. Supply the url to JIRA server, for example http://jira.sample
+1. Supply the username of a user we created under `Configuring JIRA` section, for example `gitlab`
+1. Supply the password of the user
+1. Optional: supply the JIRA api version, default is version
+1. Optional: supply the JIRA issue transition ID (issue transition to closed). This is dependant on JIRA settings, default is 2
+1. Save
+
+Now we should be able to interact with JIRA issues.
diff --git a/doc/integration/jira_issue_reference.png b/doc/integration/jira_issue_reference.png
new file mode 100644
index 00000000000..15739a22dc7
--- /dev/null
+++ b/doc/integration/jira_issue_reference.png
Binary files differ
diff --git a/doc/integration/jira_project_name.png b/doc/integration/jira_project_name.png
new file mode 100644
index 00000000000..5986fdb63fb
--- /dev/null
+++ b/doc/integration/jira_project_name.png
Binary files differ
diff --git a/doc/integration/jira_service.png b/doc/integration/jira_service.png
new file mode 100644
index 00000000000..1f6628c4371
--- /dev/null
+++ b/doc/integration/jira_service.png
Binary files differ
diff --git a/doc/integration/jira_service_close_issue.png b/doc/integration/jira_service_close_issue.png
new file mode 100644
index 00000000000..67dfc6144c4
--- /dev/null
+++ b/doc/integration/jira_service_close_issue.png
Binary files differ
diff --git a/doc/integration/jira_service_page.png b/doc/integration/jira_service_page.png
new file mode 100644
index 00000000000..69ec44e826f
--- /dev/null
+++ b/doc/integration/jira_service_page.png
Binary files differ
diff --git a/doc/integration/jira_workflow_screenshot.png b/doc/integration/jira_workflow_screenshot.png
new file mode 100644
index 00000000000..8635a32eb68
--- /dev/null
+++ b/doc/integration/jira_workflow_screenshot.png
Binary files differ
diff --git a/doc/integration/recaptcha.md b/doc/integration/recaptcha.md
new file mode 100644
index 00000000000..a301d1a613c
--- /dev/null
+++ b/doc/integration/recaptcha.md
@@ -0,0 +1,23 @@
+# reCAPTCHA
+
+GitLab leverages [Google's reCAPTCHA](https://www.google.com/recaptcha/intro/index.html)
+to protect against spam and abuse. GitLab displays the CAPTCHA form on the sign-up page
+to confirm that a real user, not a bot, is attempting to create an account.
+
+## Configuration
+
+To use reCAPTCHA, first you must create a site and private key.
+
+1. Go to the URL: https://www.google.com/recaptcha/admin
+
+2. Fill out the form necessary to obtain reCAPTCHA keys.
+
+3. Login to your GitLab server, with administrator credentials.
+
+4. Go to Applications Settings on Admin Area (`admin/application_settings`)
+
+5. Fill all recaptcha fields with keys from previous steps
+
+6. Check the `Enable reCAPTCHA` checkbox
+
+7. Save the configuration.
diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md
index bcd00cfc6bf..1be78ac1823 100644
--- a/doc/permissions/permissions.md
+++ b/doc/permissions/permissions.md
@@ -6,11 +6,11 @@ If a user is both in a project group and in the project itself, the highest perm
If a user is a GitLab administrator they receive all permissions.
-On public projects the Guest role is not enforced.
-All users will be able to create issues, leave comments, and pull or download the project code.
+On public 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](doc/workflow/add-user/add-user.md).
+documentation](../workflow/add-user/add-user.md).
## Project
diff --git a/doc/security/README.md b/doc/security/README.md
index fba6013d9c1..f34c792d005 100644
--- a/doc/security/README.md
+++ b/doc/security/README.md
@@ -6,3 +6,5 @@
- [Information exclusivity](information_exclusivity.md)
- [Reset your root password](reset_root_password.md)
- [User File Uploads](user_file_uploads.md)
+- [How we manage the CRIME vulnerability](crime_vulnerability.md)
+- [Enforce Two-Factor authentication](two_factor_authentication.md)
diff --git a/doc/security/crime_vulnerability.md b/doc/security/crime_vulnerability.md
new file mode 100644
index 00000000000..94ba5d1375d
--- /dev/null
+++ b/doc/security/crime_vulnerability.md
@@ -0,0 +1,63 @@
+# How we manage the TLS protocol CRIME vulnerability
+
+> CRIME ("Compression Ratio Info-leak Made Easy") is a security exploit against
+secret web cookies over connections using the HTTPS and SPDY protocols that also
+use data compression. When used to recover the content of secret
+authentication cookies, it allows an attacker to perform session hijacking on an
+authenticated web session, allowing the launching of further attacks.
+([CRIME](https://en.wikipedia.org/w/index.php?title=CRIME&oldid=692423806))
+
+### Description
+
+The TLS Protocol CRIME Vulnerability affects compression over HTTPS, therefore
+it warns against using SSL Compression (for example gzip) or SPDY which
+optionally uses compression as well.
+
+GitLab supports both gzip and [SPDY][ngx-spdy] and mitigates the CRIME
+vulnerability by deactivating gzip when HTTPS is enabled. You can see the
+sources of the files in question:
+
+* [Source installation NGINX file][source-nginx]
+* [Omnibus installation NGINX file][omnibus-nginx]
+
+Although SPDY is enabled in Omnibus installations, CRIME relies on compression
+(the 'C') and the default compression level in NGINX's SPDY module is 0
+(no compression).
+
+### Nessus
+
+The Nessus scanner, [reports a possible CRIME vulnerability][nessus] in GitLab
+similar to the following format:
+
+```
+Description
+
+This remote service has one of two configurations that are known to be required for the CRIME attack:
+SSL/TLS compression is enabled.
+TLS advertises the SPDY protocol earlier than version 4.
+
+...
+
+Output
+
+The following configuration indicates that the remote service may be vulnerable to the CRIME attack:
+SPDY support earlier than version 4 is advertised.
+```
+
+From the report above it is important to note that Nessus is only checking if
+TLS advertises the SPDY protocol earlier than version 4, it does not perform an
+attack nor does it check if compression is enabled. With just this approach, it
+cannot tell that SPDY's compression is disabled and not subject to the CRIME
+vulnerability.
+
+### References
+
+* Nginx ["Module ngx_http_spdy_module"][ngx-spdy]
+* Tenable Network Security, Inc. ["Transport Layer Security (TLS) Protocol CRIME Vulnerability"][nessus]
+* Wikipedia contributors, ["CRIME"][wiki-crime] Wikipedia, The Free Encyclopedia
+
+[source-nginx]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/support/nginx/gitlab-ssl
+[omnibus-nginx]: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/templates/default/nginx-gitlab-http.conf.erb
+[ngx-spdy]: http://nginx.org/en/docs/http/ngx_http_spdy_module.html
+[nessus]: https://www.tenable.com/plugins/index.php?view=single&id=62565
+[wiki-crime]: https://en.wikipedia.org/wiki/CRIME
diff --git a/doc/security/two_factor_authentication.md b/doc/security/two_factor_authentication.md
new file mode 100644
index 00000000000..4e25a1fdc3f
--- /dev/null
+++ b/doc/security/two_factor_authentication.md
@@ -0,0 +1,38 @@
+# Enforce Two-factor Authentication (2FA)
+
+Two-factor Authentication (2FA) provides an additional level of security to your
+users' GitLab account. Once enabled, in addition to supplying their username and
+password to login, they'll be prompted for a code generated by an application on
+their phone.
+
+You can read more about it here:
+[Two-factor Authentication (2FA)](doc/profile/two_factor_authentication.md)
+
+## Enabling 2FA
+
+Users on GitLab, can enable it without any admin's intervention. If you want to
+enforce everyone to setup 2FA, you can choose from two different ways:
+
+ 1. Enforce on next login
+ 2. Suggest on next login, but allow a grace period before enforcing.
+
+In the Admin area under **Settings** (`/admin/application_settings`), look for
+the "Sign-in Restrictions" area, where you can configure both.
+
+If you want 2FA enforcement to take effect on next login, change the grace
+period to `0`
+
+## Disabling 2FA for everyone
+
+There may be some special situations where you want to disable 2FA for everyone
+even when forced 2FA is disabled. There is a rake task for that:
+
+```
+# use this command if you've installed GitLab with the Omnibus package
+sudo gitlab-rake gitlab:two_factor:disable_for_all_users
+
+# if you've installed GitLab from source
+sudo -u git -H bundle exec rake gitlab:two_factor:disable_for_all_users RAILS_ENV=production
+```
+
+**IMPORTANT: this is a permanent and irreversible action. Users will have to reactivate 2FA from scratch if they want to use it again.**
diff --git a/doc/update/8.2-to-8.3.md b/doc/update/8.2-to-8.3.md
index e69c4f7ed3c..3748941b781 100644
--- a/doc/update/8.2-to-8.3.md
+++ b/doc/update/8.2-to-8.3.md
@@ -67,7 +67,7 @@ sudo -u git -H git checkout 8-3-stable-ee
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch --all
-sudo -u git -H git checkout v2.6.8
+sudo -u git -H git checkout v2.6.9
```
### 5. Update gitlab-workhorse
@@ -78,7 +78,7 @@ 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 0.4.2
+sudo -u git -H git checkout 0.5.1
sudo -u git -H make
```
@@ -99,8 +99,6 @@ 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
-# Update init.d script
-sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
```
### 7. Update configuration files
@@ -115,6 +113,12 @@ git diff origin/8-2-stable:config/gitlab.yml.example origin/8-3-stable:config/gi
#### Nginx configuration
+GitLab 8.3 introduces major changes in the NGINX configuration.
+Because all HTTP requests pass through gitlab-workhorse now a lot of
+directives need to be removed from NGINX. During future upgrades there
+should be much less changes in the NGINX configuration because of
+this.
+
View changes between the previous recommended Nginx configuration and the
current one:
@@ -134,6 +138,18 @@ 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-3-stable/lib/support/init.d/gitlab.default.example#L34
+#### Init script
+
+We updated the init script for GitLab in order to pass new
+configuration options to gitlab-workhorse. We let gitlab-workhorse
+connect to the Rails application via a Unix domain socket and we tell
+it where the 'public' directory of GitLab is.
+
+```
+cd /home/git/gitlab
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
### 8. Use Redis v2.8.0+
Previous versions of GitLab allowed Redis versions >= 2.0 to be used, but
diff --git a/doc/workflow/README.md b/doc/workflow/README.md
index d2642495c9a..3651b55f438 100644
--- a/doc/workflow/README.md
+++ b/doc/workflow/README.md
@@ -19,3 +19,4 @@
- ["Work In Progress" Merge Requests](wip_merge_requests.md)
- [Merge When Build Succeeds](merge_when_build_succeeds.md)
- [Manage large binaries with Git LFS](lfs/manage_large_binaries_with_git_lfs.md)
+- [Importing from SVN, GitHub, BitBucket, etc](importing/README.md)
diff --git a/doc/workflow/importing/README.md b/doc/workflow/importing/README.md
index 7ccf06fbd60..18e5d950866 100644
--- a/doc/workflow/importing/README.md
+++ b/doc/workflow/importing/README.md
@@ -1,13 +1,17 @@
# Migrating projects to a GitLab instance
1. [Bitbucket](import_projects_from_bitbucket.md)
-2. [GitHub](import_projects_from_github.md)
-3. [GitLab.com](import_projects_from_gitlab_com.md)
-4. [FogBugz](import_projects_from_fogbugz.md)
-4. [SVN](migrating_from_svn.md)
+1. [GitHub](import_projects_from_github.md)
+1. [GitLab.com](import_projects_from_gitlab_com.md)
+1. [FogBugz](import_projects_from_fogbugz.md)
+1. [SVN](migrating_from_svn.md)
-### Note
-* If you'd like to migrate from a self-hosted GitLab instance to GitLab.com, you can copy your repos by changing the remote and pushing to the new server; but issues and merge requests can't be imported.
+In addition to the specific migration documentation above, you can import any
+Git repository via HTTP from the New Project page. Be aware that if the
+repository is too large the import can timeout.
+
+### Migrating from self-hosted GitLab to GitLab.com
+
+You can copy your repos by changing the remote and pushing to the new server;
+but issues and merge requests can't be imported.
-* You can import any Git repository via HTTP from the New Project page.
-If the repository is too large, it can timeout.
diff --git a/doc/workflow/importing/migrating_from_svn.md b/doc/workflow/importing/migrating_from_svn.md
index 1938ccd0c26..b355a91b5a6 100644
--- a/doc/workflow/importing/migrating_from_svn.md
+++ b/doc/workflow/importing/migrating_from_svn.md
@@ -1,17 +1,78 @@
# Migrating from SVN to GitLab
-SVN stands for Subversion and is a version control system (VCS).
-Git is a distributed version control system.
+Subversion (SVN) is a central version control system (VCS) while
+Git is a distributed version control system. There are some major differences
+between the two, for more information consult your favorite search engine.
-There are some major differences between the two, for more information consult your favorite search engine.
+If you are currently using an SVN repository, you can migrate the repository
+to Git and GitLab. We recommend a hard cut over - run the migration command once
+and then have all developers start using the new GitLab repository immediately.
+Otherwise, it's hard to keep changing in sync in both directions. The conversion
+process should be run on a local workstation.
-Git has tools for migrating SVN repositories to git, namely `git svn`. You can read more about this at
-[git documentation pages](https://git-scm.com/book/en/Git-and-Other-Systems-Git-and-Subversion).
+Install `svn2git`. On all systems you can install as a Ruby gem if you already
+have Ruby and Git installed.
-Apart from the [official git documentation](https://git-scm.com/book/en/Git-and-Other-Systems-Migrating-to-Git) there is also
-user created step by step guide for migrating from SVN to GitLab.
+```bash
+sudo gem install svn2git
+```
-[Benjamin New](https://github.com/leftclickben) wrote [a guide that shows how to do a migration](https://gist.github.com/leftclickben/322b7a3042cbe97ed2af). Mirrors can be found [here](https://gitlab.com/snippets/2168) and [here](https://gist.github.com/maxlazio/f1b593b0d00aa966e9ca).
+On Debian-based Linux distributions you can install the native packages:
+
+```bash
+sudo apt-get install git-core git-svn ruby
+```
+
+Optionally, prepare an authors file so `svn2git` can map SVN authors to Git authors.
+If you choose not to create the authors file then commits will not be attributed
+to the correct GitLab user. Some users may not consider this a big issue while
+others will want to ensure they complete this step. If you choose to map authors
+you will be required to map every author that is present on changes in the SVN
+repository. If you don't, the conversion will fail and you will have to update
+the author file accordingly. The following command will search through the
+repository and output a list of authors.
+
+```bash
+svn log --quiet | grep -E "r[0-9]+ \| .+ \|" | cut -d'|' -f2 | sed 's/ //g' | sort | uniq
+```
+
+Use the output from the last command to construct the authors file.
+Create a file called `authors.txt` and add one mapping per line.
+
+```
+janedoe = Jane Doe <janedoe@example.com>
+johndoe = John Doe <johndoe@example.com>
+```
+
+If your SVN repository is in the standard format (trunk, branches, tags,
+not nested) the conversion is simple. For a non-standard repository see
+[svn2git documentation](https://github.com/nirvdrum/svn2git). The following
+command will checkout the repository and do the conversion in the current
+working directory. Be sure to create a new directory for each repository before
+running the `svn2git` command. The conversion process will take some time.
+
+```bash
+svn2git https://svn.example.com/path/to/repo --authors /path/to/authors.txt
+```
+
+If your SVN repository requires a username and password add the
+`--username <username>` and `--password <password` flags to the above command.
+`svn2git` also supports excluding certain file paths, branches, tags, etc. See
+[svn2git documentation](https://github.com/nirvdrum/svn2git) or run
+`svn2git --help` for full documentation on all of the available options.
+
+Create a new GitLab project, where you will eventually push your converted code.
+Copy the SSH or HTTP(S) repository URL from the project page. Add the GitLab
+repository as a Git remote and push all the changes. This will push all commits,
+branches and tags.
+
+```bash
+git remote add origin git@gitlab.com:<group>/<project>.git
+git push --all origin
+```
## Contribute to this guide
-We welcome all contributions that would expand this guide with instructions on how to migrate from SVN and other version control systems.
+We welcome all contributions that would expand this guide with instructions on
+how to migrate from SVN and other version control systems.
+
+
diff --git a/features/project/commits/branches.feature b/features/project/commits/branches.feature
index 5103ca12947..2c17d32154a 100644
--- a/features/project/commits/branches.feature
+++ b/features/project/commits/branches.feature
@@ -25,6 +25,7 @@ Feature: Project Commits Branches
And I click branch 'improve/awesome' delete link
Then I should not see branch 'improve/awesome'
+ @javascript
Scenario: I create a branch with invalid name
Given I visit project branches page
And I click new branch link
diff --git a/features/project/create.feature b/features/project/create.feature
index a86079143e5..27136798e36 100644
--- a/features/project/create.feature
+++ b/features/project/create.feature
@@ -1,3 +1,4 @@
+@project-create
Feature: Project Create
In order to get access to project sections
A user with ability to create a project
diff --git a/features/project/issues/award_emoji.feature b/features/project/issues/award_emoji.feature
index 0ce99e855c6..9a06fdc2ee6 100644
--- a/features/project/issues/award_emoji.feature
+++ b/features/project/issues/award_emoji.feature
@@ -14,6 +14,17 @@ Feature: Award Emoji
And I can remove it by clicking to icon
@javascript
+ Scenario: I can see the list of emoji categories
+ Given I click to emoji-picker
+ Then I can see the activity and food categories
+
+ @javascript
+ Scenario: I can search emoji
+ Given I click to emoji-picker
+ And I search "hand"
+ Then I see search result for "hand"
+
+ @javascript
Scenario: I add award emoji using regular comment
- Given I leave comment with a single emoji
- Then I have award added
+ Given I leave comment with a single emoji
+ Then I have award added
diff --git a/features/project/merge_requests/accept.feature b/features/project/merge_requests/accept.feature
index d5e4f2b0bd8..330ec8ae0fe 100644
--- a/features/project/merge_requests/accept.feature
+++ b/features/project/merge_requests/accept.feature
@@ -13,6 +13,14 @@ Feature: Project Merge Requests Acceptance
And I should not see the Remove Source Branch button
@javascript
+ Scenario: Accepting the Merge Request when URL has an anchor
+ Given I am on the Merge Request detail with note anchor page
+ When I click on "Remove source branch" option
+ And I click on Accept Merge Request
+ Then I should see merge request merged
+ And I should not see the Remove Source Branch button
+
+ @javascript
Scenario: Accepting the Merge Request without removing the source branch
Given I am on the Merge Request detail page
When I click on Accept Merge Request
diff --git a/features/project/service.feature b/features/project/service.feature
index ff3e7a0b38e..3a7b8308524 100644
--- a/features/project/service.feature
+++ b/features/project/service.feature
@@ -55,6 +55,12 @@ Feature: Project Services
And I fill email on push settings
Then I should see email on push service settings saved
+ Scenario: Activate JIRA service
+ When I visit project "Shop" services page
+ And I click jira service link
+ And I fill jira settings
+ Then I should see jira service settings saved
+
Scenario: Activate Irker (IRC Gateway) service
When I visit project "Shop" services page
And I click Irker service link
diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature
index 02159ee3776..a8c276b949e 100644
--- a/features/project/source/browse_files.feature
+++ b/features/project/source/browse_files.feature
@@ -24,6 +24,12 @@ Feature: Project Source Browse Files
Given I click on "New file" link in repo
Then I can see new file page
+ Scenario: I can create file when I don't have write access
+ Given I don't have write access
+ And I click on "New file" link in repo
+ Then I should see a notice about a new fork having been created
+ Then I can see new file page
+
@javascript
Scenario: I can create and commit file
Given I click on "New file" link in repo
@@ -35,6 +41,17 @@ Feature: Project Source Browse Files
And I should see its new content
@javascript
+ Scenario: I can create and commit file when I don't have write access
+ Given I don't have write access
+ And I click on "New file" link in repo
+ And I edit code
+ And I fill the new file name
+ And I fill the commit message
+ And I click on "Commit Changes"
+ Then I am redirected to the fork's new merge request page
+ And I can see the new commit message
+
+ @javascript
Scenario: I can create and commit file with new lines at the end of file
Given I click on "New file" link in repo
And I edit code with new lines at end of file
@@ -46,6 +63,17 @@ Feature: Project Source Browse Files
And I should see its content with new lines preserved at end of file
@javascript
+ Scenario: I can create and commit file and specify new branch
+ Given I click on "New file" link in repo
+ And I edit code
+ And I fill the new file name
+ And I fill the commit message
+ And I fill the new branch name
+ And I click on "Commit Changes"
+ Then I am redirected to the new merge request page
+ And I should see its new content
+
+ @javascript
Scenario: I can upload file and commit
Given I click on "Upload file" link in repo
And I upload a new text file
@@ -57,6 +85,19 @@ Feature: Project Source Browse Files
And I can see the new commit message
@javascript
+ Scenario: I can upload file and commit when I don't have write access
+ Given I don't have write access
+ And I click on "Upload file" link in repo
+ Then I should see a notice about a new fork having been created
+ When I click on "Upload file" link in repo
+ And I upload a new text file
+ And I fill the upload file commit message
+ And I click on "Upload file"
+ Then I can see the new text file
+ And I am redirected to the fork's new merge request page
+ And I can see the new commit message
+
+ @javascript
Scenario: I can replace file and commit
Given I click on ".gitignore" file in repo
And I see the ".gitignore"
@@ -68,15 +109,19 @@ Feature: Project Source Browse Files
And I can see the replacement commit message
@javascript
- Scenario: I can create and commit file and specify new branch
- Given I click on "New file" link in repo
- And I edit code
- And I fill the new file name
- And I fill the commit message
- And I fill the new branch name
- And I click on "Commit Changes"
- Then I am redirected to the new merge request page
- And I should see its new content
+ Scenario: I can replace file and commit when I don't have write access
+ Given I don't have write access
+ And I click on ".gitignore" file in repo
+ And I see the ".gitignore"
+ And I click on "Replace"
+ Then I should see a notice about a new fork having been created
+ When I click on "Replace"
+ And I replace it with a text file
+ And I fill the replace file commit message
+ And I click on "Replace file"
+ Then I can see the new text file
+ And I am redirected to the fork's new merge request page
+ And I can see the replacement commit message
@javascript
Scenario: I can create file in empty repo
@@ -117,6 +162,14 @@ Feature: Project Source Browse Files
And I click button "Edit"
Then I can edit code
+ @javascript
+ Scenario: I can edit file when I don't have write access
+ Given I don't have write access
+ And I click on ".gitignore" file in repo
+ And I click button "Edit"
+ Then I should see a notice about a new fork having been created
+ And I can edit code
+
Scenario: If the file is binary the edit link is hidden
Given I visit a binary file in the repo
Then I cannot see the edit button
@@ -132,6 +185,17 @@ Feature: Project Source Browse Files
And I should see its new content
@javascript
+ Scenario: I can edit and commit file when I don't have write access
+ Given I don't have write access
+ And I click on ".gitignore" file in repo
+ And I click button "Edit"
+ And I edit code
+ And I fill the commit message
+ And I click on "Commit Changes"
+ Then I am redirected to the fork's new merge request page
+ And I can see the new commit message
+
+ @javascript
Scenario: I can edit and commit file to new branch
Given I click on ".gitignore" file in repo
And I click button "Edit"
@@ -162,6 +226,17 @@ Feature: Project Source Browse Files
Then I am redirected to the new merge request page
@javascript
+ Scenario: I can create directory in repo when I don't have write access
+ Given I don't have write access
+ When I click on "New directory" link in repo
+ Then I should see a notice about a new fork having been created
+ When I click on "New directory" link in repo
+ And I fill the new directory name
+ And I fill the commit message
+ And I click on "Create directory"
+ Then I am redirected to the fork's new merge request page
+
+ @javascript
Scenario: I attempt to create an existing directory
When I click on "New directory" link in repo
And I fill an existing directory name
@@ -188,6 +263,19 @@ Feature: Project Source Browse Files
Then I am redirected to the files URL
And I don't see the ".gitignore"
+ @javascript
+ Scenario: I can delete file and commit when I don't have write access
+ Given I don't have write access
+ And I click on ".gitignore" file in repo
+ And I see the ".gitignore"
+ And I click on "Delete"
+ Then I should see a notice about a new fork having been created
+ When I click on "Delete"
+ And I fill the commit message
+ And I click on "Delete file"
+ Then I am redirected to the fork's new merge request page
+ And I can see the new commit message
+
Scenario: I can browse directory with Browse Dir
Given I click on files directory
And I click on History link
diff --git a/features/project/star.feature b/features/project/star.feature
index a45f9c470ea..618f44fe6dc 100644
--- a/features/project/star.feature
+++ b/features/project/star.feature
@@ -1,3 +1,4 @@
+@project-stars
Feature: Project Star
Scenario: New projects have 0 stars
Given public project "Community"
diff --git a/features/steps/explore/groups.rb b/features/steps/explore/groups.rb
index 87cd33c37eb..87f32e70d59 100644
--- a/features/steps/explore/groups.rb
+++ b/features/steps/explore/groups.rb
@@ -75,18 +75,18 @@ class Spinach::Features::ExploreGroups < Spinach::FeatureSteps
name: projectname,
path: "#{groupname}-#{projectname}",
visibility_level: visibility_level
- )
+ )
create(:issue,
title: "#{projectname} feature",
project: project
- )
+ )
create(:merge_request,
title: "#{projectname} feature implemented",
source_project: project,
target_project: project
- )
+ )
create(:closed_issue_event,
project: project
- )
+ )
end
end
diff --git a/features/steps/explore/projects.rb b/features/steps/explore/projects.rb
index f819dec2192..742ba5d71f6 100644
--- a/features/steps/explore/projects.rb
+++ b/features/steps/explore/projects.rb
@@ -61,11 +61,11 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
create(:issue,
title: "Bug",
project: public_project
- )
+ )
create(:issue,
title: "New feature",
project: public_project
- )
+ )
visit namespace_project_issues_path(public_project.namespace, public_project)
end
@@ -80,11 +80,11 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
create(:issue,
title: "Internal Bug",
project: internal_project
- )
+ )
create(:issue,
title: "New internal feature",
project: internal_project
- )
+ )
visit namespace_project_issues_path(internal_project.namespace, internal_project)
end
@@ -104,7 +104,7 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
title: "Bug fix for public project",
source_project: public_project,
target_project: public_project,
- )
+ )
end
step 'I should see list of merge requests for "Community" project' do
@@ -121,7 +121,7 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
title: "Feature implemented",
source_project: internal_project,
target_project: internal_project
- )
+ )
end
step 'I should see list of merge requests for "Internal" project' do
diff --git a/features/steps/groups.rb b/features/steps/groups.rb
index f5e3fee61c0..4c5122d1b7d 100644
--- a/features/steps/groups.rb
+++ b/features/steps/groups.rb
@@ -85,7 +85,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
step 'I should see new group "Owned" avatar' do
expect(owned_group.avatar).to be_instance_of AvatarUploader
- expect(owned_group.avatar.url).to eq "/uploads/group/avatar/#{ Group.find_by(name:"Owned").id }/banana_sample.gif"
+ expect(owned_group.avatar.url).to eq "/uploads/group/avatar/#{Group.find_by(name:"Owned").id}/banana_sample.gif"
end
step 'I should see the "Remove avatar" button' do
diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb
index 40b2aa7c357..0305f7e6da0 100644
--- a/features/steps/profile/profile.rb
+++ b/features/steps/profile/profile.rb
@@ -34,7 +34,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
step 'I should see new avatar' do
expect(@user.avatar).to be_instance_of AvatarUploader
- expect(@user.avatar.url).to eq "/uploads/user/avatar/#{ @user.id }/banana_sample.gif"
+ expect(@user.avatar.url).to eq "/uploads/user/avatar/#{@user.id}/banana_sample.gif"
end
step 'I should see the "Remove avatar" button' do
diff --git a/features/steps/project/commits/branches.rb b/features/steps/project/commits/branches.rb
index 338f5e8d3ee..0a42931147d 100644
--- a/features/steps/project/commits/branches.rb
+++ b/features/steps/project/commits/branches.rb
@@ -61,7 +61,8 @@ class Spinach::Features::ProjectCommitsBranches < Spinach::FeatureSteps
end
step 'I should see new an error that branch is invalid' do
- expect(page).to have_content 'Branch name invalid'
+ expect(page).to have_content 'Branch name is invalid'
+ expect(page).to have_content "can't contain spaces"
end
step 'I should see new an error that ref is invalid' do
diff --git a/features/steps/project/create.rb b/features/steps/project/create.rb
index f90218f3791..8a0e8fc2b6c 100644
--- a/features/steps/project/create.rb
+++ b/features/steps/project/create.rb
@@ -26,7 +26,8 @@ class Spinach::Features::ProjectCreate < Spinach::FeatureSteps
end
step 'I click on HTTP' do
- click_button 'HTTP'
+ find('#clone-dropdown').click
+ find('#http-selector').click
end
step 'Remote url should update to http link' do
@@ -34,7 +35,8 @@ class Spinach::Features::ProjectCreate < Spinach::FeatureSteps
end
step 'If I click on SSH' do
- click_button 'SSH'
+ find('#clone-dropdown').click
+ find('#ssh-selector').click
end
step 'Remote url should update to ssh link' do
diff --git a/features/steps/project/forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb
index d3675060994..cbdce78dc0c 100644
--- a/features/steps/project/forked_merge_requests.rb
+++ b/features/steps/project/forked_merge_requests.rb
@@ -41,7 +41,8 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
click_button "Compare branches and continue"
- expect(page).to have_content "New Merge Request"
+ expect(page).to have_css("h3.page-title", text: "New Merge Request")
+
fill_in "merge_request_title", with: "Merge Request On Forked Project"
end
diff --git a/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb
index 325eaf2ea6a..2c2ed08655e 100644
--- a/features/steps/project/issues/award_emoji.rb
+++ b/features/steps/project/issues/award_emoji.rb
@@ -15,22 +15,31 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
end
step 'I click to emoji in the picker' do
- page.within '.awards-menu' do
- page.first('img').click
+ page.within '.emoji-menu-content' do
+ page.first('.emoji-icon').click
end
end
step 'I can remove it by clicking to icon' do
page.within '.awards' do
- page.first('.award').click
- expect(page).to_not have_selector '.award'
+ expect do
+ page.find('.award.active').click
+ sleep 0.3
+ end.to change{ page.all(".award").size }.from(3).to(2)
+ end
+ end
+
+ step 'I can see the activity and food categories' do
+ page.within '.emoji-menu' do
+ expect(page).to_not have_selector 'Activity'
+ expect(page).to_not have_selector 'Food'
end
end
step 'I have award added' do
page.within '.awards' do
expect(page).to have_selector '.award'
- expect(page.find('.award .counter')).to have_content '1'
+ expect(page.find('.award.active .counter')).to have_content '1'
end
end
@@ -45,4 +54,16 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
click_button 'Add Comment'
end
end
+
+ step 'I search "hand"' do
+ page.within('.emoji-menu-content') do
+ fill_in 'emoji_search', with: 'hand'
+ end
+ end
+
+ step 'I see search result for "hand"' do
+ page.within '.emoji-menu-content' do
+ expect(page).to have_selector '[data-emoji="raised_hand"]'
+ end
+ end
end
diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb
index 4a7ff21d385..8e8c9c57452 100644
--- a/features/steps/project/issues/issues.rb
+++ b/features/steps/project/issues/issues.rb
@@ -59,15 +59,14 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
end
step 'I click "author" dropdown' do
- first('.ajax-users-select').click
+ first('#s2id_author_id').click
end
step 'I see current user as the first user' do
- expect(page).to have_selector('.user-result', visible: true, count: 4)
+ expect(page).to have_selector('.user-result', visible: true, count: 3)
users = page.all('.user-name')
- expect(users[0].text).to eq 'Any Assignee'
- expect(users[1].text).to eq 'Unassigned'
- expect(users[2].text).to eq current_user.name
+ expect(users[0].text).to eq 'Any Author'
+ expect(users[1].text).to eq current_user.name
end
step 'I submit new issue "500 error on profile"' do
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index 0d340d97ff9..be993d11093 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -273,7 +273,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
step 'I should see merged request' do
- page.within '.issue-box' do
+ page.within '.status-box' do
expect(page).to have_content "Merged"
end
end
@@ -283,7 +283,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
step 'I should see reopened merge request "Bug NS-04"' do
- page.within '.issue-box' do
+ page.within '.status-box' do
expect(page).to have_content "Open"
end
end
diff --git a/features/steps/project/merge_requests/acceptance.rb b/features/steps/project/merge_requests/acceptance.rb
index 383c055c4ef..2685f5fd6b4 100644
--- a/features/steps/project/merge_requests/acceptance.rb
+++ b/features/steps/project/merge_requests/acceptance.rb
@@ -6,6 +6,10 @@ class Spinach::Features::ProjectMergeRequestsAcceptance < Spinach::FeatureSteps
visit merge_request_path(@merge_request)
end
+ step 'I am on the Merge Request detail with note anchor page' do
+ visit merge_request_path(@merge_request, anchor: 'note_123')
+ end
+
step 'I click on "Remove source branch" option' do
check('Remove source branch')
end
diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb
index 9ca7c8ebbc7..37bf52b4a95 100644
--- a/features/steps/project/project.rb
+++ b/features/steps/project/project.rb
@@ -37,7 +37,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps
step 'I should see new project avatar' do
expect(@project.avatar).to be_instance_of AvatarUploader
url = @project.avatar.url
- expect(url).to eq "/uploads/project/avatar/#{ @project.id }/banana_sample.gif"
+ expect(url).to eq "/uploads/project/avatar/#{@project.id}/banana_sample.gif"
end
step 'I should see the "Remove avatar" button' do
diff --git a/features/steps/project/services.rb b/features/steps/project/services.rb
index ed3957ca873..536199ddb4f 100644
--- a/features/steps/project/services.rb
+++ b/features/steps/project/services.rb
@@ -173,6 +173,24 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
expect(find_field('Sound').find('option[selected]').value).to eq 'bike'
end
+ step 'I click jira service link' do
+ click_link 'JIRA'
+ end
+
+ step 'I fill jira settings' do
+ fill_in 'Project url', with: 'http://jira.example'
+ fill_in 'Username', with: 'gitlab'
+ fill_in 'Password', with: 'gitlab'
+ fill_in 'Api url', with: 'http://jira.example/rest/api/2'
+ click_button 'Save'
+ end
+
+ step 'I should see jira service settings saved' do
+ expect(find_field('Project url').value).to eq 'http://jira.example'
+ expect(find_field('Username').value).to eq 'gitlab'
+ expect(find_field('Api url').value).to eq 'http://jira.example/rest/api/2'
+ end
+
step 'I click Atlassian Bamboo CI service link' do
click_link 'Atlassian Bamboo CI'
end
diff --git a/features/steps/project/snippets.rb b/features/steps/project/snippets.rb
index a3aef9bf8c3..504654f90dd 100644
--- a/features/steps/project/snippets.rb
+++ b/features/steps/project/snippets.rb
@@ -42,7 +42,7 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps
end
step 'I click link "Edit"' do
- page.within ".page-title" do
+ page.within ".detail-page-header" do
click_link "Edit"
end
end
diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb
index b88709620ab..d08935aa101 100644
--- a/features/steps/project/source/browse_files.rb
+++ b/features/steps/project/source/browse_files.rb
@@ -5,6 +5,12 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
include SharedPaths
include RepoHelpers
+ step "I don't have write access" do
+ @project = create(:project, name: "Other Project", path: "other-project")
+ @project.team << [@user, :reporter]
+ visit namespace_project_tree_path(@project.namespace, @project, root_ref)
+ end
+
step 'I should see files from repository' do
expect(page).to have_content "VERSION"
expect(page).to have_content ".gitignore"
@@ -75,7 +81,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I fill the new branch name' do
- fill_in :new_branch, with: 'new_branch_name', visible: true
+ fill_in :target_branch, with: 'new_branch_name', visible: true
end
step 'I fill the new file name with an illegal name' do
@@ -87,7 +93,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I fill the commit message' do
- fill_in :commit_message, with: 'Not yet a commit message.', visible: true
+ fill_in :commit_message, with: 'New commit message', visible: true
end
step 'I click link "Diff"' do
@@ -103,7 +109,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I click on "Delete"' do
- click_button 'Delete'
+ click_on 'Delete'
end
step 'I click on "Delete file"' do
@@ -111,7 +117,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I click on "Replace"' do
- click_button "Replace"
+ click_on "Replace"
end
step 'I click on "Replace file"' do
@@ -124,7 +130,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
step 'I click on "New file" link in repo' do
find('.add-to-tree').click
- click_link 'Create file'
+ click_link 'New file'
end
step 'I click on "Upload file" link in repo' do
@@ -155,7 +161,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I can see the new commit message' do
- expect(page).to have_content "New upload commit message"
+ expect(page).to have_content "New commit message"
end
step 'I upload a new text file' do
@@ -164,7 +170,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
step 'I fill the upload file commit message' do
page.within('#modal-upload-blob') do
- fill_in :commit_message, with: 'New upload commit message'
+ fill_in :commit_message, with: 'New commit message'
end
end
@@ -238,22 +244,27 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I am redirected to the new file' do
- expect(current_path).to eq(namespace_project_blob_path(
- @project.namespace, @project, 'master/' + new_file_name))
+ expect(current_path).to eq(
+ namespace_project_blob_path(@project.namespace, @project, 'master/' + new_file_name))
end
step 'I am redirected to the new file with directory' do
- expect(current_path).to eq(namespace_project_blob_path(
- @project.namespace, @project, 'master/' + new_file_name_with_directory))
+ expect(current_path).to eq(
+ namespace_project_blob_path(@project.namespace, @project, 'master/' + new_file_name_with_directory))
end
step 'I am redirected to the new merge request page' do
expect(current_path).to eq(new_namespace_project_merge_request_path(@project.namespace, @project))
end
+ step "I am redirected to the fork's new merge request page" do
+ fork = @user.fork_of(@project)
+ expect(current_path).to eq(new_namespace_project_merge_request_path(fork.namespace, fork))
+ end
+
step 'I am redirected to the root directory' do
- expect(current_path).to eq(namespace_project_tree_path(
- @project.namespace, @project, 'master/'))
+ expect(current_path).to eq(
+ namespace_project_tree_path(@project.namespace, @project, 'master'))
end
step "I don't see the permalink link" do
@@ -332,8 +343,12 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
expect(page).to have_content 'Permalink'
expect(page).not_to have_content 'Edit'
expect(page).not_to have_content 'Blame'
- expect(page).not_to have_content 'Delete'
- expect(page).not_to have_content 'Replace'
+ expect(page).to have_content 'Delete'
+ expect(page).to have_content 'Replace'
+ end
+
+ step 'I should see a notice about a new fork having been created' do
+ expect(page).to have_content "You're not allowed to make changes to this project directly. A fork of this project has been created that you can make changes in, so you can submit a merge request."
end
private
diff --git a/features/steps/project/star.rb b/features/steps/project/star.rb
index bd2e0619cdd..9f7c748a3b7 100644
--- a/features/steps/project/star.rb
+++ b/features/steps/project/star.rb
@@ -32,6 +32,6 @@ class Spinach::Features::ProjectStar < Spinach::FeatureSteps
protected
def has_n_stars(n)
- expect(page).to have_css(".star-btn .count", text: n, visible: true)
+ expect(page).to have_css(".star-count", text: n, visible: true)
end
end
diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb
index dd466cde28d..c6a0ae2ba38 100644
--- a/features/steps/shared/diff_note.rb
+++ b/features/steps/shared/diff_note.rb
@@ -166,7 +166,7 @@ module SharedDiffNote
end
step 'I should see add a diff comment button' do
- expect(page).to have_css('.js-add-diff-note-button', visible: true)
+ expect(page).to have_css('.js-add-diff-note-button')
end
step 'I should see an empty diff comment form' do
diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb
index c74a5fd3bc7..b33bd332655 100644
--- a/features/steps/shared/paths.rb
+++ b/features/steps/shared/paths.rb
@@ -212,8 +212,8 @@ module SharedPaths
end
step 'I visit a binary file in the repo' do
- visit namespace_project_blob_path(@project.namespace, @project, File.join(
- root_ref, 'files/images/logo-black.png'))
+ visit namespace_project_blob_path(@project.namespace, @project,
+ File.join(root_ref, 'files/images/logo-black.png'))
end
step "I visit my project's commits page" do
@@ -316,8 +316,8 @@ module SharedPaths
end
step 'I am on the ".gitignore" edit file page' do
- expect(current_path).to eq(namespace_project_edit_blob_path(
- @project.namespace, @project, File.join(root_ref, '.gitignore')))
+ expect(current_path).to eq(
+ namespace_project_edit_blob_path(@project.namespace, @project, File.join(root_ref, '.gitignore')))
end
step 'I visit project source page for "6d39438"' do
diff --git a/features/steps/snippets/snippets.rb b/features/steps/snippets/snippets.rb
index 80d1ddeef05..023032e679f 100644
--- a/features/steps/snippets/snippets.rb
+++ b/features/steps/snippets/snippets.rb
@@ -13,7 +13,7 @@ class Spinach::Features::Snippets < Spinach::FeatureSteps
end
step 'I click link "Edit"' do
- page.within ".page-title" do
+ page.within ".detail-page-header" do
click_link "Edit"
end
end
diff --git a/fixtures/emojis/aliases.json b/fixtures/emojis/aliases.json
new file mode 100644
index 00000000000..547ce7978b3
--- /dev/null
+++ b/fixtures/emojis/aliases.json
@@ -0,0 +1,367 @@
+{
+ "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",
+ "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",
+ "beach_with_umbrella":"beach",
+ "bellhop_bell":"bellhop",
+ "bouquet_of_flowers":"bouquet2",
+ "bullhorn_with_sound_waves":"bullhorn_waves",
+ "pocket calculator":"calculator",
+ "spiral_calendar_pad":"calendar_spiral",
+ "card_file_box":"card_box",
+ "tape_cartridge":"cartridge",
+ "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",
+ "couch_and_lamp":"couch",
+ "couple_with_heart_mm":"couple_mm",
+ "couple_with_heart_ww":"couple_ww",
+ "lower_left_crayon":"crayon",
+ "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",
+ "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",
+ "flame":"fire",
+ "oncoming_fire_engine":"fire_engine_oncoming",
+ "ac":"flag_ac",
+ "ad":"flag_ad",
+ "ae":"flag_ae",
+ "af":"flag_af",
+ "ag":"flag_ag",
+ "ai":"flag_ai",
+ "al":"flag_al",
+ "am":"flag_am",
+ "ao":"flag_ao",
+ "ar":"flag_ar",
+ "at":"flag_at",
+ "au":"flag_au",
+ "aw":"flag_aw",
+ "az":"flag_az",
+ "ba":"flag_ba",
+ "bb":"flag_bb",
+ "bd":"flag_bd",
+ "be":"flag_be",
+ "bf":"flag_bf",
+ "bg":"flag_bg",
+ "bh":"flag_bh",
+ "bi":"flag_bi",
+ "bj":"flag_bj",
+ "waving_black_flag":"flag_black",
+ "bm":"flag_bm",
+ "bn":"flag_bn",
+ "bo":"flag_bo",
+ "br":"flag_br",
+ "bs":"flag_bs",
+ "bt":"flag_bt",
+ "bw":"flag_bw",
+ "by":"flag_by",
+ "bz":"flag_bz",
+ "ca":"flag_ca",
+ "congo":"flag_cd",
+ "cf":"flag_cf",
+ "cg":"flag_cg",
+ "ch":"flag_ch",
+ "ci":"flag_ci",
+ "chile":"flag_cl",
+ "cm":"flag_cm",
+ "cn":"flag_cn",
+ "co":"flag_co",
+ "cr":"flag_cr",
+ "cu":"flag_cu",
+ "cv":"flag_cv",
+ "cy":"flag_cy",
+ "cz":"flag_cz",
+ "de":"flag_de",
+ "dj":"flag_dj",
+ "dk":"flag_dk",
+ "dm":"flag_dm",
+ "do":"flag_do",
+ "dz":"flag_dz",
+ "ec":"flag_ec",
+ "ee":"flag_ee",
+ "eg":"flag_eg",
+ "eh":"flag_eh",
+ "er":"flag_er",
+ "es":"flag_es",
+ "et":"flag_et",
+ "fi":"flag_fi",
+ "fj":"flag_fj",
+ "fk":"flag_fk",
+ "fm":"flag_fm",
+ "fo":"flag_fo",
+ "fr":"flag_fr",
+ "ga":"flag_ga",
+ "gb":"flag_gb",
+ "gd":"flag_gd",
+ "ge":"flag_ge",
+ "gh":"flag_gh",
+ "gi":"flag_gi",
+ "gl":"flag_gl",
+ "gm":"flag_gm",
+ "gn":"flag_gn",
+ "gq":"flag_gq",
+ "gr":"flag_gr",
+ "gt":"flag_gt",
+ "gu":"flag_gu",
+ "gw":"flag_gw",
+ "gy":"flag_gy",
+ "hk":"flag_hk",
+ "hn":"flag_hn",
+ "hr":"flag_hr",
+ "ht":"flag_ht",
+ "hu":"flag_hu",
+ "indonesia":"flag_id",
+ "ie":"flag_ie",
+ "il":"flag_il",
+ "in":"flag_in",
+ "iq":"flag_iq",
+ "ir":"flag_ir",
+ "is":"flag_is",
+ "it":"flag_it",
+ "je":"flag_je",
+ "jm":"flag_jm",
+ "jo":"flag_jo",
+ "jp":"flag_jp",
+ "ke":"flag_ke",
+ "kg":"flag_kg",
+ "kh":"flag_kh",
+ "ki":"flag_ki",
+ "km":"flag_km",
+ "kn":"flag_kn",
+ "kp":"flag_kp",
+ "kr":"flag_kr",
+ "kw":"flag_kw",
+ "ky":"flag_ky",
+ "kz":"flag_kz",
+ "la":"flag_la",
+ "lb":"flag_lb",
+ "lc":"flag_lc",
+ "li":"flag_li",
+ "lk":"flag_lk",
+ "lr":"flag_lr",
+ "ls":"flag_ls",
+ "lt":"flag_lt",
+ "lu":"flag_lu",
+ "lv":"flag_lv",
+ "ly":"flag_ly",
+ "ma":"flag_ma",
+ "mc":"flag_mc",
+ "md":"flag_md",
+ "me":"flag_me",
+ "mg":"flag_mg",
+ "mh":"flag_mh",
+ "mk":"flag_mk",
+ "ml":"flag_ml",
+ "mm":"flag_mm",
+ "mn":"flag_mn",
+ "mo":"flag_mo",
+ "mr":"flag_mr",
+ "ms":"flag_ms",
+ "mt":"flag_mt",
+ "mu":"flag_mu",
+ "mv":"flag_mv",
+ "mw":"flag_mw",
+ "mx":"flag_mx",
+ "my":"flag_my",
+ "mz":"flag_mz",
+ "na":"flag_na",
+ "nc":"flag_nc",
+ "ne":"flag_ne",
+ "nigeria":"flag_ng",
+ "ni":"flag_ni",
+ "nl":"flag_nl",
+ "no":"flag_no",
+ "np":"flag_np",
+ "nr":"flag_nr",
+ "nu":"flag_nu",
+ "nz":"flag_nz",
+ "om":"flag_om",
+ "pa":"flag_pa",
+ "pe":"flag_pe",
+ "pf":"flag_pf",
+ "pg":"flag_pg",
+ "ph":"flag_ph",
+ "pk":"flag_pk",
+ "pl":"flag_pl",
+ "pr":"flag_pr",
+ "ps":"flag_ps",
+ "pt":"flag_pt",
+ "pw":"flag_pw",
+ "py":"flag_py",
+ "qa":"flag_qa",
+ "ro":"flag_ro",
+ "rs":"flag_rs",
+ "ru":"flag_ru",
+ "rw":"flag_rw",
+ "saudiarabia":"flag_sa",
+ "saudi":"flag_sa",
+ "sb":"flag_sb",
+ "sc":"flag_sc",
+ "sd":"flag_sd",
+ "se":"flag_se",
+ "sg":"flag_sg",
+ "sh":"flag_sh",
+ "si":"flag_si",
+ "sk":"flag_sk",
+ "sl":"flag_sl",
+ "sm":"flag_sm",
+ "sn":"flag_sn",
+ "so":"flag_so",
+ "sr":"flag_sr",
+ "st":"flag_st",
+ "sv":"flag_sv",
+ "sy":"flag_sy",
+ "sz":"flag_sz",
+ "td":"flag_td",
+ "tg":"flag_tg",
+ "th":"flag_th",
+ "tj":"flag_tj",
+ "tl":"flag_tl",
+ "turkmenistan":"flag_tm",
+ "tn":"flag_tn",
+ "to":"flag_to",
+ "tr":"flag_tr",
+ "tt":"flag_tt",
+ "tuvalu":"flag_tv",
+ "tw":"flag_tw",
+ "tz":"flag_tz",
+ "ua":"flag_ua",
+ "ug":"flag_ug",
+ "us":"flag_us",
+ "uy":"flag_uy",
+ "uz":"flag_uz",
+ "va":"flag_va",
+ "vc":"flag_vc",
+ "ve":"flag_ve",
+ "vi":"flag_vi",
+ "vn":"flag_vn",
+ "vu":"flag_vu",
+ "wf":"flag_wf",
+ "waving_white_flag":"flag_white",
+ "ws":"flag_ws",
+ "xk":"flag_xk",
+ "ye":"flag_ye",
+ "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",
+ "frame_with_picture":"frame_photo",
+ "frame_with_tiles":"frame_tiles",
+ "frame_with_an_x":"frame_x",
+ "anguished":"frowning",
+ "raised_hand_with_fingers_splayed":"hand_splayed",
+ "reversed_raised_hand_with_fingers_splayed":"hand_splayed_reverse",
+ "reversed_victory_hand":"hand_victory",
+ "heart_with_tip_on_the_left":"heart_tip",
+ "house_buildings":"homes",
+ "derelict_house_building":"house_abandoned",
+ "circled_information_source":"info",
+ "desert_island":"island",
+ "up_pointing_military_airplane":"jet_up",
+ "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",
+ "satisfied":"laughing",
+ "left_hand_telephone_receiver":"left_receiver",
+ "man_in_business_suit_levitating":"levitate",
+ "weight_lifter":"lifter",
+ "light_mark":"light_check_mark",
+ "world_map":"map",
+ "sports_medal":"medal",
+ "studio_microphone":"microphone2",
+ "reversed_hand_with_middle_finger_extended":"middle_finger",
+ "lightning_mood_bubble":"mood_bubble_lightning",
+ "lightning_mood":"mood_lightning",
+ "racing_motorcycle":"motorcycle",
+ "snow_capped_mountain":"mountain_snow",
+ "one_button_mouse":"mouse_one",
+ "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",
+ "oil_drum":"oil",
+ "grandma":"older_woman",
+ "optical_disc_icon":"optical_disk",
+ "lower_left_paintbrush":"paintbrush",
+ "linked_paperclips":"paperclips",
+ "national_park":"park",
+ "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",
+ "no_piracy":"piracy",
+ "shit":"poop",
+ "hankey":"poop",
+ "poo":"poop",
+ "prohibited_sign":"prohibited",
+ "film_projector":"projector",
+ "racing_car":"race_car",
+ "railroad_track":"railway_track",
+ "right_speaker_with_one_sound_wave":"right_speaker_one",
+ "right_speaker_with_three_sound_waves":"right_speaker_three",
+ "skeleton":"skull",
+ "slightly_frowning_face":"slight_frown",
+ "slightly_smiling_face":"slight_smile",
+ "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",
+ "portable_stereo":"stereo",
+ "black_touchtone_telephone":"telephone_black",
+ "white_touchtone_telephone":"telephone_white",
+ "left_thought_bubble":"thought_left",
+ "right_thought_bubble":"thought_right",
+ "reversed_thumbs_down_sign":"thumbs_down_reverse",
+ "reversed_thumbs_up_sign":"thumbs_up_reverse",
+ "-1":"thumbsdown",
+ "+1":"thumbsup",
+ "admission_tickets":"tickets",
+ "hammer_and_wrench":"tools",
+ "diesel_locomotive":"train_diesel",
+ "triangle_with_rounded_corners":"triangle_round",
+ "turned_ok_hand_sign":"turned_ok_hand",
+ "raised_hand_with_part_between_middle_and_ring_fingers":"vulcan",
+ "left_writing_hand":"writing_hand"
+} \ No newline at end of file
diff --git a/fixtures/emojis/index.json b/fixtures/emojis/index.json
new file mode 100644
index 00000000000..60ef2399e14
--- /dev/null
+++ b/fixtures/emojis/index.json
@@ -0,0 +1,13376 @@
+{
+ "100": {
+ "unicode": "1F4AF",
+ "unicode_alternates": [],
+ "name": "hundred points symbol",
+ "shortname": ":100:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["numbers", "perfect", "score", "100", "percent", "a", "plus", "perfect", "school", "quiz", "score", "test", "exam"],
+ "moji": "💯"
+ },
+ "1234": {
+ "unicode": "1F522",
+ "unicode_alternates": [],
+ "name": "input symbol for numbers",
+ "shortname": ":1234:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blue-square", "numbers"],
+ "moji": "🔢"
+ },
+ "8ball": {
+ "unicode": "1F3B1",
+ "unicode_alternates": [],
+ "name": "billiards",
+ "shortname": ":8ball:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["pool", "billiards", "eight ball", "pool", "pocket ball", "cue"],
+ "moji": "🎱"
+ },
+ "a": {
+ "unicode": "1F170",
+ "unicode_alternates": [],
+ "name": "negative squared latin capital letter a",
+ "shortname": ":a:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["alphabet", "letter", "red-square"],
+ "moji": "🅰"
+ },
+ "ab": {
+ "unicode": "1F18E",
+ "unicode_alternates": [],
+ "name": "negative squared ab",
+ "shortname": ":ab:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["alphabet", "red-square"],
+ "moji": "🆎"
+ },
+ "abc": {
+ "unicode": "1F524",
+ "unicode_alternates": [],
+ "name": "input symbol for latin letters",
+ "shortname": ":abc:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["alphabet", "blue-square"],
+ "moji": "🔤"
+ },
+ "abcd": {
+ "unicode": "1F521",
+ "unicode_alternates": [],
+ "name": "input symbol for latin small letters",
+ "shortname": ":abcd:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["alphabet", "blue-square"],
+ "moji": "🔡"
+ },
+ "accept": {
+ "unicode": "1F251",
+ "unicode_alternates": [],
+ "name": "circled ideograph accept",
+ "shortname": ":accept:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["agree", "chinese", "good", "kanji", "ok", "yes"],
+ "moji": "🉑"
+ },
+ "aerial_tramway": {
+ "unicode": "1F6A1",
+ "unicode_alternates": [],
+ "name": "aerial tramway",
+ "shortname": ":aerial_tramway:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["transportation", "vehicle", "aerial", "tram", "tramway", "cable", "transport"],
+ "moji": "🚡"
+ },
+ "airplane": {
+ "unicode": "2708",
+ "unicode_alternates": ["2708-FE0F"],
+ "name": "airplane",
+ "shortname": ":airplane:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["flight", "transportation", "vehicle", "airplane", "plane", "airport", "travel", "airlines", "fly", "jet", "jumbo", "boeing", "airbus"],
+ "moji": "✈"
+ },
+ "airplane_arriving": {
+ "unicode": "1F6EC",
+ "unicode_alternates": [],
+ "name": "airplane arriving",
+ "shortname": ":airplane_arriving:",
+ "category": "travel_places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["flight", "transportation", "vehicle", "plane", "airport", "travel", "airlines", "fly", "jet", "jumbo", "boeing", "airbus"]
+ },
+ "airplane_departure": {
+ "unicode": "1F6EB",
+ "unicode_alternates": [],
+ "name": "airplane departure",
+ "shortname": ":airplane_departure:",
+ "category": "travel_places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["flight", "transportation", "vehicle", "plane", "airport", "travel", "airlines", "fly", "jet", "jumbo", "boeing", "airbus", "leaving"]
+ },
+ "airplane_northeast": {
+ "unicode": "1F6EA",
+ "unicode_alternates": [],
+ "name": "northeast-pointing airplane",
+ "shortname": ":airplane_northeast:",
+ "category": "travel_places",
+ "aliases": [":northeast_pointing_airplane:"],
+ "aliases_ascii": [],
+ "keywords": ["plane", "travel"]
+ },
+ "airplane_small": {
+ "unicode": "1F6E9",
+ "unicode_alternates": [],
+ "name": "small airplane",
+ "shortname": ":airplane_small:",
+ "category": "travel_places",
+ "aliases": [":small_airplane:"],
+ "aliases_ascii": [],
+ "keywords": ["flight", "transportation", "vehicle", "plane", "airport", "travel", "airlines", "fly", "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:"],
+ "aliases_ascii": [],
+ "keywords": ["plane", "travel"]
+ },
+ "alarm_clock": {
+ "unicode": "23F0",
+ "unicode_alternates": [],
+ "name": "alarm clock",
+ "shortname": ":alarm_clock:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["time", "wake"],
+ "moji": "⏰"
+ },
+ "alien": {
+ "unicode": "1F47D",
+ "unicode_alternates": [],
+ "name": "extraterrestrial alien",
+ "shortname": ":alien:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["UFO", "paul", "alien", "ufo"],
+ "moji": "👽"
+ },
+ "ambulance": {
+ "unicode": "1F691",
+ "unicode_alternates": [],
+ "name": "ambulance",
+ "shortname": ":ambulance:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["911", "health", "ambulance", "emergency", "medical", "help", "assistance"],
+ "moji": "🚑"
+ },
+ "anchor": {
+ "unicode": "2693",
+ "unicode_alternates": ["2693-FE0F"],
+ "name": "anchor",
+ "shortname": ":anchor:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["ferry", "ship", "anchor", "ship", "boat", "ocean", "harbor", "marina", "shipyard", "sailor", "tattoo"],
+ "moji": "⚓"
+ },
+ "angel": {
+ "unicode": "1F47C",
+ "unicode_alternates": [],
+ "name": "baby angel",
+ "shortname": ":angel:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["baby", "angel", "halo", "cupid", "wings", "halo", "heaven", "wings", "jesus"],
+ "moji": "👼"
+ },
+ "anger": {
+ "unicode": "1F4A2",
+ "unicode_alternates": [],
+ "name": "anger symbol",
+ "shortname": ":anger:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["anger", "angry", "mad"],
+ "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",
+ "aliases": [":right_anger_bubble:"],
+ "aliases_ascii": [],
+ "keywords": ["speech", "balloon", "talk", "mood", "conversation", "communication", "comic", "angry"]
+ },
+ "angry": {
+ "unicode": "1F620",
+ "unicode_alternates": [],
+ "name": "angry face",
+ "shortname": ":angry:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [">:(", ">:-(", ":@"],
+ "keywords": ["angry", "livid", "mad", "vexed", "irritated", "annoyed", "face", "frustrated", "mad"],
+ "moji": "😠"
+ },
+ "anguished": {
+ "unicode": "1F627",
+ "unicode_alternates": [],
+ "name": "anguished face",
+ "shortname": ":anguished:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["face", "nervous", "stunned", "pain", "anguish", "ouch", "misery", "distress", "grief"],
+ "moji": "😧"
+ },
+ "ant": {
+ "unicode": "1F41C",
+ "unicode_alternates": [],
+ "name": "ant",
+ "shortname": ":ant:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "insect", "ant", "queen", "insect", "team"],
+ "moji": "🐜"
+ },
+ "apple": {
+ "unicode": "1F34E",
+ "unicode_alternates": [],
+ "name": "red apple",
+ "shortname": ":apple:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["fruit", "mac", "apple", "fruit", "electronics", "red", "doctor", "teacher", "school", "core"],
+ "moji": "🍎"
+ },
+ "aquarius": {
+ "unicode": "2652",
+ "unicode_alternates": ["2652-FE0F"],
+ "name": "aquarius",
+ "shortname": ":aquarius:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["aquarius", "water", "bearer", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "purple-square", "sign", "zodiac", "horoscope"],
+ "moji": "♒"
+ },
+ "aries": {
+ "unicode": "2648",
+ "unicode_alternates": ["2648-FE0F"],
+ "name": "aries",
+ "shortname": ":aries:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["aries", "ram", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "purple-square", "sign", "zodiac", "horoscope"],
+ "moji": "♈"
+ },
+ "arrow_backward": {
+ "unicode": "25C0",
+ "unicode_alternates": ["25C0-FE0F"],
+ "name": "black left-pointing triangle",
+ "shortname": ":arrow_backward:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["arrow", "blue-square"],
+ "moji": "◀"
+ },
+ "arrow_double_down": {
+ "unicode": "23EC",
+ "unicode_alternates": [],
+ "name": "black down-pointing double triangle",
+ "shortname": ":arrow_double_down:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["arrow", "blue-square"],
+ "moji": "⏬"
+ },
+ "arrow_double_up": {
+ "unicode": "23EB",
+ "unicode_alternates": [],
+ "name": "black up-pointing double triangle",
+ "shortname": ":arrow_double_up:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["arrow", "blue-square"],
+ "moji": "⏫"
+ },
+ "arrow_down": {
+ "unicode": "2B07",
+ "unicode_alternates": ["2B07-FE0F"],
+ "name": "downwards black arrow",
+ "shortname": ":arrow_down:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["arrow", "blue-square"],
+ "moji": "⬇"
+ },
+ "arrow_down_small": {
+ "unicode": "1F53D",
+ "unicode_alternates": [],
+ "name": "down-pointing small red triangle",
+ "shortname": ":arrow_down_small:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["arrow", "blue-square"],
+ "moji": "🔽"
+ },
+ "arrow_forward": {
+ "unicode": "25B6",
+ "unicode_alternates": ["25B6-FE0F"],
+ "name": "black right-pointing triangle",
+ "shortname": ":arrow_forward:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["arrow", "blue-square"],
+ "moji": "▶"
+ },
+ "arrow_heading_down": {
+ "unicode": "2935",
+ "unicode_alternates": ["2935-FE0F"],
+ "name": "arrow pointing rightwards then curving downwards",
+ "shortname": ":arrow_heading_down:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["arrow", "blue-square"],
+ "moji": "⤵"
+ },
+ "arrow_heading_up": {
+ "unicode": "2934",
+ "unicode_alternates": ["2934-FE0F"],
+ "name": "arrow pointing rightwards then curving upwards",
+ "shortname": ":arrow_heading_up:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["arrow", "blue-square"],
+ "moji": "⤴"
+ },
+ "arrow_left": {
+ "unicode": "2B05",
+ "unicode_alternates": ["2B05-FE0F"],
+ "name": "leftwards black arrow",
+ "shortname": ":arrow_left:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["arrow", "blue-square", "previous"],
+ "moji": "⬅"
+ },
+ "arrow_lower_left": {
+ "unicode": "2199",
+ "unicode_alternates": ["2199-FE0F"],
+ "name": "south west arrow",
+ "shortname": ":arrow_lower_left:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["arrow", "blue-square"],
+ "moji": "↙"
+ },
+ "arrow_lower_right": {
+ "unicode": "2198",
+ "unicode_alternates": ["2198-FE0F"],
+ "name": "south east arrow",
+ "shortname": ":arrow_lower_right:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["arrow", "blue-square"],
+ "moji": "↘"
+ },
+ "arrow_right": {
+ "unicode": "27A1",
+ "unicode_alternates": ["27A1-FE0F"],
+ "name": "black rightwards arrow",
+ "shortname": ":arrow_right:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blue-square", "next"],
+ "moji": "➡"
+ },
+ "arrow_right_hook": {
+ "unicode": "21AA",
+ "unicode_alternates": ["21AA-FE0F"],
+ "name": "rightwards arrow with hook",
+ "shortname": ":arrow_right_hook:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blue-square"],
+ "moji": "↪"
+ },
+ "arrow_up": {
+ "unicode": "2B06",
+ "unicode_alternates": ["2B06-FE0F"],
+ "name": "upwards black arrow",
+ "shortname": ":arrow_up:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blue-square"],
+ "moji": "⬆"
+ },
+ "arrow_up_down": {
+ "unicode": "2195",
+ "unicode_alternates": ["2195-FE0F"],
+ "name": "up down arrow",
+ "shortname": ":arrow_up_down:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blue-square"],
+ "moji": "↕"
+ },
+ "arrow_up_small": {
+ "unicode": "1F53C",
+ "unicode_alternates": [],
+ "name": "up-pointing small red triangle",
+ "shortname": ":arrow_up_small:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blue-square"],
+ "moji": "🔼"
+ },
+ "arrow_upper_left": {
+ "unicode": "2196",
+ "unicode_alternates": ["2196-FE0F"],
+ "name": "north west arrow",
+ "shortname": ":arrow_upper_left:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blue-square"],
+ "moji": "↖"
+ },
+ "arrow_upper_right": {
+ "unicode": "2197",
+ "unicode_alternates": ["2197-FE0F"],
+ "name": "north east arrow",
+ "shortname": ":arrow_upper_right:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blue-square"],
+ "moji": "↗"
+ },
+ "arrows_clockwise": {
+ "unicode": "1F503",
+ "unicode_alternates": [],
+ "name": "clockwise downwards and upwards open circle arrows",
+ "shortname": ":arrows_clockwise:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["sync"],
+ "moji": "🔃"
+ },
+ "arrows_counterclockwise": {
+ "unicode": "1F504",
+ "unicode_alternates": [],
+ "name": "anticlockwise downwards and upwards open circle ar",
+ "shortname": ":arrows_counterclockwise:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blue-square", "sync"],
+ "moji": "🔄"
+ },
+ "art": {
+ "unicode": "1F3A8",
+ "unicode_alternates": [],
+ "name": "artist palette",
+ "shortname": ":art:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["design", "draw", "paint", "artist", "palette", "art", "colors", "paint", "draw", "brush", "pastels", "oils"],
+ "moji": "🎨"
+ },
+ "articulated_lorry": {
+ "unicode": "1F69B",
+ "unicode_alternates": [],
+ "name": "articulated lorry",
+ "shortname": ":articulated_lorry:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["cars", "transportation", "vehicle", "truck", "delivery", "semi", "lorry", "articulated"],
+ "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"]
+ },
+ "astonished": {
+ "unicode": "1F632",
+ "unicode_alternates": [],
+ "name": "astonished face",
+ "shortname": ":astonished:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["face", "xox", "shocked", "surprise", "astonished"],
+ "moji": "😲"
+ },
+ "athletic_shoe": {
+ "unicode": "1F45F",
+ "unicode_alternates": [],
+ "name": "athletic shoe",
+ "shortname": ":athletic_shoe:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["shoes", "sports"],
+ "moji": "👟"
+ },
+ "atm": {
+ "unicode": "1F3E7",
+ "unicode_alternates": [],
+ "name": "automated teller machine",
+ "shortname": ":atm:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["atm", "cash", "withdrawal", "money", "deposit", "financial", "bank", "adam", "payday", "bank", "blue-square", "cash", "money", "payment"],
+ "moji": "🏧"
+ },
+ "b": {
+ "unicode": "1F171",
+ "unicode_alternates": [],
+ "name": "negative squared latin capital letter b",
+ "shortname": ":b:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["alphabet", "letter", "red-square"],
+ "moji": "🅱"
+ },
+ "baby": {
+ "unicode": "1F476",
+ "unicode_alternates": [],
+ "name": "baby",
+ "shortname": ":baby:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["boy", "child", "infant"],
+ "moji": "👶"
+ },
+ "baby_bottle": {
+ "unicode": "1F37C",
+ "unicode_alternates": [],
+ "name": "baby bottle",
+ "shortname": ":baby_bottle:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["container", "food", "baby", "bottle", "milk", "mother", "nipple", "newborn", "formula"],
+ "moji": "🍼"
+ },
+ "baby_chick": {
+ "unicode": "1F424",
+ "unicode_alternates": [],
+ "name": "baby chick",
+ "shortname": ":baby_chick:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "chicken", "chick", "baby", "bird", "chicken", "young", "woman", "cute"],
+ "moji": "🐤"
+ },
+ "baby_symbol": {
+ "unicode": "1F6BC",
+ "unicode_alternates": [],
+ "name": "baby symbol",
+ "shortname": ":baby_symbol:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["child", "orange-square", "baby", "crawl", "newborn", "human", "diaper", "small", "babe"],
+ "moji": "🚼"
+ },
+ "back": {
+ "unicode": "1F519",
+ "unicode_alternates": [],
+ "name": "back with leftwards arrow above",
+ "shortname": ":back:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["arrow"],
+ "moji": "🔙"
+ },
+ "baggage_claim": {
+ "unicode": "1F6C4",
+ "unicode_alternates": [],
+ "name": "baggage claim",
+ "shortname": ":baggage_claim:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["airport", "blue-square", "transport", "bag", "baggage", "luggage", "travel"],
+ "moji": "🛄"
+ },
+ "balloon": {
+ "unicode": "1F388",
+ "unicode_alternates": [],
+ "name": "balloon",
+ "shortname": ":balloon:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["celebration", "party", "balloon", "birthday", "celebration", "helium", "gas", "children", "float"],
+ "moji": "🎈"
+ },
+ "ballot_box": {
+ "unicode": "1F5F3",
+ "unicode_alternates": [],
+ "name": "ballot box with ballot",
+ "shortname": ":ballot_box:",
+ "category": "objects_symbols",
+ "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:"],
+ "aliases_ascii": [],
+ "keywords": ["mark", "vote"]
+ },
+ "ballot_box_with_check": {
+ "unicode": "2611",
+ "unicode_alternates": ["2611-FE0F"],
+ "name": "ballot box with check",
+ "shortname": ":ballot_box_with_check:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["agree", "ok"],
+ "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",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["nature", "plant", "vegetable", "pine", "bamboo", "decoration", "new", "years", "spirits", "harvest", "prosperity", "longevity", "fortune", "luck", "welcome", "farming", "agriculture"],
+ "moji": "🎍"
+ },
+ "banana": {
+ "unicode": "1F34C",
+ "unicode_alternates": [],
+ "name": "banana",
+ "shortname": ":banana:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["food", "fruit", "banana", "peel", "bunch"],
+ "moji": "🍌"
+ },
+ "bangbang": {
+ "unicode": "203C",
+ "unicode_alternates": ["203C-FE0F"],
+ "name": "double exclamation mark",
+ "shortname": ":bangbang:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["exclamation", "surprise"],
+ "moji": "‼"
+ },
+ "bank": {
+ "unicode": "1F3E6",
+ "unicode_alternates": [],
+ "name": "bank",
+ "shortname": ":bank:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["building"],
+ "moji": "🏦"
+ },
+ "bar_chart": {
+ "unicode": "1F4CA",
+ "unicode_alternates": [],
+ "name": "bar chart",
+ "shortname": ":bar_chart:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["graph", "presentation", "stats"],
+ "moji": "📊"
+ },
+ "barber": {
+ "unicode": "1F488",
+ "unicode_alternates": [],
+ "name": "barber pole",
+ "shortname": ":barber:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["hair", "salon", "style"],
+ "moji": "💈"
+ },
+ "baseball": {
+ "unicode": "26BE",
+ "unicode_alternates": ["26BE-FE0F"],
+ "name": "baseball",
+ "shortname": ":baseball:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["MLB", "balls", "sports"],
+ "moji": "⚾"
+ },
+ "basketball": {
+ "unicode": "1F3C0",
+ "unicode_alternates": [],
+ "name": "basketball and hoop",
+ "shortname": ":basketball:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["NBA", "balls", "sports", "basketball", "bball", "dribble", "hoop", "net", "swish", "rip city"],
+ "moji": "🏀"
+ },
+ "bath": {
+ "unicode": "1F6C0",
+ "unicode_alternates": [],
+ "name": "bath",
+ "shortname": ":bath:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["clean", "shower", "bath", "tub", "basin", "wash", "bubble", "soak", "bathroom", "soap", "water", "clean", "shampoo", "lather", "water"],
+ "moji": "🛀"
+ },
+ "bathtub": {
+ "unicode": "1F6C1",
+ "unicode_alternates": [],
+ "name": "bathtub",
+ "shortname": ":bathtub:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["clean", "shower", "bath", "tub", "basin", "wash", "bubble", "soak", "bathroom", "soap", "water", "clean", "shampoo", "lather", "water"],
+ "moji": "🛁"
+ },
+ "battery": {
+ "unicode": "1F50B",
+ "unicode_alternates": [],
+ "name": "battery",
+ "shortname": ":battery:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["energy", "power", "sustain"],
+ "moji": "🔋"
+ },
+ "beach": {
+ "unicode": "1F3D6",
+ "unicode_alternates": [],
+ "name": "beach with umbrella",
+ "shortname": ":beach:",
+ "category": "travel_places",
+ "aliases": [":beach_with_umbrella:"],
+ "aliases_ascii": [],
+ "keywords": ["sand", "sun", "surf", "vacation", "relaxation", "tanning", "tan", "swimming"]
+ },
+ "bear": {
+ "unicode": "1F43B",
+ "unicode_alternates": [],
+ "name": "bear face",
+ "shortname": ":bear:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "nature"],
+ "moji": "🐻"
+ },
+ "bed": {
+ "unicode": "1F6CF",
+ "unicode_alternates": [],
+ "name": "bed",
+ "shortname": ":bed:",
+ "category": "travel_places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["sleep", "sex", "queen", "full", "twin", "king", "mattress"]
+ },
+ "bee": {
+ "unicode": "1F41D",
+ "unicode_alternates": [],
+ "name": "honeybee",
+ "shortname": ":bee:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "insect", "bee", "queen", "buzz", "flower", "pollen", "sting", "honey", "hive", "bumble", "pollination"],
+ "moji": "🐝"
+ },
+ "beer": {
+ "unicode": "1F37A",
+ "unicode_alternates": [],
+ "name": "beer mug",
+ "shortname": ":beer:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["beverage", "drink", "drunk", "party", "pub", "relax", "beer", "hops", "mug", "barley", "malt", "yeast", "portland", "oregon", "brewery", "micro", "pint", "boot"],
+ "moji": "🍺"
+ },
+ "beers": {
+ "unicode": "1F37B",
+ "unicode_alternates": [],
+ "name": "clinking beer mugs",
+ "shortname": ":beers:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["beverage", "drink", "drunk", "party", "pub", "relax", "beer", "beers", "cheers", "mug", "toast", "celebrate", "pub", "bar", "jolly", "hops", "clink"],
+ "moji": "🍻"
+ },
+ "beetle": {
+ "unicode": "1F41E",
+ "unicode_alternates": [],
+ "name": "lady beetle",
+ "shortname": ":beetle:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["insect", "nature", "lady", "bug", "ladybug", "ladybird", "beetle", "cow", "lady cow", "insect", "endearment"],
+ "moji": "🐞"
+ },
+ "beginner": {
+ "unicode": "1F530",
+ "unicode_alternates": [],
+ "name": "japanese symbol for beginner",
+ "shortname": ":beginner:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["badge", "shield"],
+ "moji": "🔰"
+ },
+ "bell": {
+ "unicode": "1F514",
+ "unicode_alternates": [],
+ "name": "bell",
+ "shortname": ":bell:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["chime", "christmas", "notification", "sound", "xmas"],
+ "moji": "🔔"
+ },
+ "bellhop": {
+ "unicode": "1F6CE",
+ "unicode_alternates": [],
+ "name": "bellhop bell",
+ "shortname": ":bellhop:",
+ "category": "travel_places",
+ "aliases": [":bellhop_bell:"],
+ "aliases_ascii": [],
+ "keywords": ["hotel", "porter", "ding"]
+ },
+ "bento": {
+ "unicode": "1F371",
+ "unicode_alternates": [],
+ "name": "bento box",
+ "shortname": ":bento:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["box", "food", "japanese", "bento", "japanese", "rice", "meal", "box", "obento", "convenient", "lunchbox"],
+ "moji": "🍱"
+ },
+ "bicyclist": {
+ "unicode": "1F6B4",
+ "unicode_alternates": [],
+ "name": "bicyclist",
+ "shortname": ":bicyclist:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["bike", "exercise", "hipster", "sports", "bicyclist", "road", "bike", "pedal", "bicycle", "transportation"],
+ "moji": "🚴"
+ },
+ "bike": {
+ "unicode": "1F6B2",
+ "unicode_alternates": [],
+ "name": "bicycle",
+ "shortname": ":bike:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["bicycle", "exercise", "hipster", "sports", "bike", "pedal", "bicycle", "transportation"],
+ "moji": "🚲"
+ },
+ "bikini": {
+ "unicode": "1F459",
+ "unicode_alternates": [],
+ "name": "bikini",
+ "shortname": ":bikini:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["beach", "fashion", "female", "girl", "swimming", "woman"],
+ "moji": "👙"
+ },
+ "bird": {
+ "unicode": "1F426",
+ "unicode_alternates": [],
+ "name": "bird",
+ "shortname": ":bird:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "fly", "nature", "tweet"],
+ "moji": "🐦"
+ },
+ "birthday": {
+ "unicode": "1F382",
+ "unicode_alternates": [],
+ "name": "birthday cake",
+ "shortname": ":birthday:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["cake", "party", "birthday", "birth", "cake", "dessert", "wish", "celebrate"],
+ "moji": "🎂"
+ },
+ "black_circle": {
+ "unicode": "26AB",
+ "unicode_alternates": ["26AB-FE0F"],
+ "name": "medium black circle",
+ "shortname": ":black_circle:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["shape"],
+ "moji": "⚫"
+ },
+ "black_joker": {
+ "unicode": "1F0CF",
+ "unicode_alternates": [],
+ "name": "playing card black joker",
+ "shortname": ":black_joker:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["cards", "game", "poker"],
+ "moji": "🃏"
+ },
+ "black_large_square": {
+ "unicode": "2B1B",
+ "unicode_alternates": ["2B1B-FE0F"],
+ "name": "black large square",
+ "shortname": ":black_large_square:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["shape"],
+ "moji": "⬛"
+ },
+ "black_medium_small_square": {
+ "unicode": "25FE",
+ "unicode_alternates": ["25FE-FE0F"],
+ "name": "black medium small square",
+ "shortname": ":black_medium_small_square:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "◾"
+ },
+ "black_medium_square": {
+ "unicode": "25FC",
+ "unicode_alternates": ["25FC-FE0F"],
+ "name": "black medium square",
+ "shortname": ":black_medium_square:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["shape"],
+ "moji": "◼"
+ },
+ "black_nib": {
+ "unicode": "2712",
+ "unicode_alternates": ["2712-FE0F"],
+ "name": "black nib",
+ "shortname": ":black_nib:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["pen", "stationery"],
+ "moji": "✒"
+ },
+ "black_small_square": {
+ "unicode": "25AA",
+ "unicode_alternates": ["25AA-FE0F"],
+ "name": "black small square",
+ "shortname": ":black_small_square:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "▪"
+ },
+ "black_square_button": {
+ "unicode": "1F532",
+ "unicode_alternates": [],
+ "name": "black square button",
+ "shortname": ":black_square_button:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["frame"],
+ "moji": "🔲"
+ },
+ "blossom": {
+ "unicode": "1F33C",
+ "unicode_alternates": [],
+ "name": "blossom",
+ "shortname": ":blossom:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["flowers", "nature", "yellow", "blossom", "daisy", "flower"],
+ "moji": "🌼"
+ },
+ "blowfish": {
+ "unicode": "1F421",
+ "unicode_alternates": [],
+ "name": "blowfish",
+ "shortname": ":blowfish:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["food", "nature", "ocean", "sea", "blowfish", "pufferfish", "puffer", "ballonfish", "toadfish", "fugu fish", "sushi"],
+ "moji": "🐡"
+ },
+ "blue_book": {
+ "unicode": "1F4D8",
+ "unicode_alternates": [],
+ "name": "blue book",
+ "shortname": ":blue_book:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["knowledge", "library", "read"],
+ "moji": "📘"
+ },
+ "blue_car": {
+ "unicode": "1F699",
+ "unicode_alternates": [],
+ "name": "recreational vehicle",
+ "shortname": ":blue_car:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["car", "suv", "car", "wagon", "automobile"],
+ "moji": "🚙"
+ },
+ "blue_heart": {
+ "unicode": "1F499",
+ "unicode_alternates": [],
+ "name": "blue heart",
+ "shortname": ":blue_heart:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["affection", "like", "love", "valentines", "blue", "heart", "love", "stability", "truth", "loyalty", "trust"],
+ "moji": "💙"
+ },
+ "blush": {
+ "unicode": "1F60A",
+ "unicode_alternates": [],
+ "name": "smiling face with smiling eyes",
+ "shortname": ":blush:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["crush", "embarrassed", "face", "flushed", "happy", "shy", "smile", "smiling", "smile", "smiley"],
+ "moji": "😊"
+ },
+ "boar": {
+ "unicode": "1F417",
+ "unicode_alternates": [],
+ "name": "boar",
+ "shortname": ":boar:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "nature"],
+ "moji": "🐗"
+ },
+ "bomb": {
+ "unicode": "1F4A3",
+ "unicode_alternates": [],
+ "name": "bomb",
+ "shortname": ":bomb:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["boom", "explode"],
+ "moji": "💣"
+ },
+ "book": {
+ "unicode": "1F4D6",
+ "unicode_alternates": [],
+ "name": "open book",
+ "shortname": ":book:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["library", "literature"],
+ "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": [],
+ "name": "bookmark",
+ "shortname": ":bookmark:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["favorite"],
+ "moji": "🔖"
+ },
+ "bookmark_tabs": {
+ "unicode": "1F4D1",
+ "unicode_alternates": [],
+ "name": "bookmark tabs",
+ "shortname": ":bookmark_tabs:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["favorite"],
+ "moji": "📑"
+ },
+ "books": {
+ "unicode": "1F4DA",
+ "unicode_alternates": [],
+ "name": "books",
+ "shortname": ":books:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["library", "literature"],
+ "moji": "📚"
+ },
+ "boom": {
+ "unicode": "1F4A5",
+ "unicode_alternates": [],
+ "name": "collision symbol",
+ "shortname": ":boom:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["bomb", "explode", "explosion", "boom", "bang", "collision", "fire", "emphasis", "wow", "bam"],
+ "moji": "💥"
+ },
+ "boot": {
+ "unicode": "1F462",
+ "unicode_alternates": [],
+ "name": "womans boots",
+ "shortname": ":boot:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["fashion", "shoes"],
+ "moji": "👢"
+ },
+ "bouquet": {
+ "unicode": "1F490",
+ "unicode_alternates": [],
+ "name": "bouquet",
+ "shortname": ":bouquet:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["flowers", "nature"],
+ "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",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["boy", "male", "man", "sorry", "bow", "respect", "curtsy", "bend"],
+ "moji": "🙇"
+ },
+ "bowling": {
+ "unicode": "1F3B3",
+ "unicode_alternates": [],
+ "name": "bowling",
+ "shortname": ":bowling:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["fun", "play", "sports", "bowl", "bowling", "ball", "pin", "strike", "spare", "game"],
+ "moji": "🎳"
+ },
+ "boy": {
+ "unicode": "1F466",
+ "unicode_alternates": [],
+ "name": "boy",
+ "shortname": ":boy:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["guy", "male", "man"],
+ "moji": "👦"
+ },
+ "boys_symbol": {
+ "unicode": "1F6C9",
+ "unicode_alternates": [],
+ "name": "boys symbol",
+ "shortname": ":boys_symbol:",
+ "category": "objects_symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["male", "child"]
+ },
+ "bread": {
+ "unicode": "1F35E",
+ "unicode_alternates": [],
+ "name": "bread",
+ "shortname": ":bread:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["breakfast", "food", "toast", "wheat", "bread", "loaf", "yeast"],
+ "moji": "🍞"
+ },
+ "bride_with_veil": {
+ "unicode": "1F470",
+ "unicode_alternates": [],
+ "name": "bride with veil",
+ "shortname": ":bride_with_veil:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["couple", "marriage", "wedding", "bride", "wedding", "planning", "veil", "gown", "dress", "engagement", "white"],
+ "moji": "👰"
+ },
+ "bridge_at_night": {
+ "unicode": "1F309",
+ "unicode_alternates": [],
+ "name": "bridge at night",
+ "shortname": ":bridge_at_night:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["photo", "sanfrancisco", "bridge", "night", "water", "road", "evening", "suspension", "golden", "gate"],
+ "moji": "🌉"
+ },
+ "briefcase": {
+ "unicode": "1F4BC",
+ "unicode_alternates": [],
+ "name": "briefcase",
+ "shortname": ":briefcase:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["business", "documents", "work"],
+ "moji": "💼"
+ },
+ "broken_heart": {
+ "unicode": "1F494",
+ "unicode_alternates": [],
+ "name": "broken heart",
+ "shortname": ":broken_heart:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": ["</3"],
+ "keywords": ["sad", "sorry"],
+ "moji": "💔"
+ },
+ "bug": {
+ "unicode": "1F41B",
+ "unicode_alternates": [],
+ "name": "bug",
+ "shortname": ":bug:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["insect", "nature", "bug", "insect", "virus", "error"],
+ "moji": "🐛"
+ },
+ "bulb": {
+ "unicode": "1F4A1",
+ "unicode_alternates": [],
+ "name": "electric light bulb",
+ "shortname": ":bulb:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["electricity", "light", "idea", "bulb", "light"],
+ "moji": "💡"
+ },
+ "bullettrain_front": {
+ "unicode": "1F685",
+ "unicode_alternates": [],
+ "name": "high-speed train with bullet nose",
+ "shortname": ":bullettrain_front:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["transportation", "train", "bullet", "rail"],
+ "moji": "🚅"
+ },
+ "bullettrain_side": {
+ "unicode": "1F684",
+ "unicode_alternates": [],
+ "name": "high-speed train",
+ "shortname": ":bullettrain_side:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["transportation", "vehicle", "train", "bullet", "rail"],
+ "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"]
+ },
+ "bus": {
+ "unicode": "1F68C",
+ "unicode_alternates": [],
+ "name": "bus",
+ "shortname": ":bus:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["car", "transportation", "vehicle", "bus", "school", "city", "transportation", "public"],
+ "moji": "🚌"
+ },
+ "busstop": {
+ "unicode": "1F68F",
+ "unicode_alternates": [],
+ "name": "bus stop",
+ "shortname": ":busstop:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["transportation", "bus", "stop", "city", "transport", "transportation"],
+ "moji": "🚏"
+ },
+ "bust_in_silhouette": {
+ "unicode": "1F464",
+ "unicode_alternates": [],
+ "name": "bust in silhouette",
+ "shortname": ":bust_in_silhouette:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["human", "man", "person", "user", "silhouette", "person", "user", "member", "account", "guest", "icon", "avatar", "profile", "me", "myself", "i"],
+ "moji": "👤"
+ },
+ "busts_in_silhouette": {
+ "unicode": "1F465",
+ "unicode_alternates": [],
+ "name": "busts in silhouette",
+ "shortname": ":busts_in_silhouette:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["group", "human", "man", "person", "team", "user", "silhouette", "silhouettes", "people", "user", "members", "accounts", "relationship", "shadow"],
+ "moji": "👥"
+ },
+ "cactus": {
+ "unicode": "1F335",
+ "unicode_alternates": [],
+ "name": "cactus",
+ "shortname": ":cactus:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["nature", "plant", "vegetable", "cactus", "desert", "drought", "spike", "poke"],
+ "moji": "🌵"
+ },
+ "cake": {
+ "unicode": "1F370",
+ "unicode_alternates": [],
+ "name": "shortcake",
+ "shortname": ":cake:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["desert", "food", "cake", "short", "dessert", "strawberry"],
+ "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": [],
+ "name": "tear-off calendar",
+ "shortname": ":calendar:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["schedule"],
+ "moji": "📆"
+ },
+ "calendar_spiral": {
+ "unicode": "1F5D3",
+ "unicode_alternates": [],
+ "name": "spiral calendar pad",
+ "shortname": ":calendar_spiral:",
+ "category": "objects_symbols",
+ "aliases": [":spiral_calendar_pad:"],
+ "aliases_ascii": [],
+ "keywords": ["schedule", "date", "day"]
+ },
+ "calling": {
+ "unicode": "1F4F2",
+ "unicode_alternates": [],
+ "name": "mobile phone with rightwards arrow at left",
+ "shortname": ":calling:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["incoming", "iphone"],
+ "moji": "📲"
+ },
+ "camel": {
+ "unicode": "1F42B",
+ "unicode_alternates": [],
+ "name": "bactrian camel",
+ "shortname": ":camel:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "hot", "nature", "bactrian", "camel", "hump", "desert", "central asia", "heat", "hot", "water", "hump day", "wednesday", "sex"],
+ "moji": "🐫"
+ },
+ "camera": {
+ "unicode": "1F4F7",
+ "unicode_alternates": [],
+ "name": "camera",
+ "shortname": ":camera:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["gadgets", "photo"],
+ "moji": "📷"
+ },
+ "camera_with_flash": {
+ "unicode": "1F4F8",
+ "unicode_alternates": [],
+ "name": "camera with flash",
+ "shortname": ":camera_with_flash:",
+ "category": "objects_symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["photo", "picture"]
+ },
+ "camping": {
+ "unicode": "1F3D5",
+ "unicode_alternates": [],
+ "name": "camping",
+ "shortname": ":camping:",
+ "category": "travel_places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["outdoors", "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"]
+ },
+ "cancer": {
+ "unicode": "264B",
+ "unicode_alternates": ["264B-FE0F"],
+ "name": "cancer",
+ "shortname": ":cancer:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["cancer", "crab", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "sign", "zodiac", "horoscope"],
+ "moji": "♋"
+ },
+ "candle": {
+ "unicode": "1F56F",
+ "unicode_alternates": [],
+ "name": "candle",
+ "shortname": ":candle:",
+ "category": "objects_symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["light", "wax"]
+ },
+ "candy": {
+ "unicode": "1F36C",
+ "unicode_alternates": [],
+ "name": "candy",
+ "shortname": ":candy:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["desert", "snack", "candy", "sugar", "sweet", "hard"],
+ "moji": "🍬"
+ },
+ "capital_abcd": {
+ "unicode": "1F520",
+ "unicode_alternates": [],
+ "name": "input symbol for latin capital letters",
+ "shortname": ":capital_abcd:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["alphabet", "blue-square", "words"],
+ "moji": "🔠"
+ },
+ "capricorn": {
+ "unicode": "2651",
+ "unicode_alternates": ["2651-FE0F"],
+ "name": "capricorn",
+ "shortname": ":capricorn:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["capricorn", "sea-goat", "goat-horned", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "sign", "zodiac", "horoscope"],
+ "moji": "♑"
+ },
+ "card_box": {
+ "unicode": "1F5C3",
+ "unicode_alternates": [],
+ "name": "card file box",
+ "shortname": ":card_box:",
+ "category": "objects_symbols",
+ "aliases": [":card_file_box:"],
+ "aliases_ascii": [],
+ "keywords": ["index", "organization"]
+ },
+ "card_index": {
+ "unicode": "1F4C7",
+ "unicode_alternates": [],
+ "name": "card index",
+ "shortname": ":card_index:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["business", "stationery"],
+ "moji": "📇"
+ },
+ "carousel_horse": {
+ "unicode": "1F3A0",
+ "unicode_alternates": [],
+ "name": "carousel horse",
+ "shortname": ":carousel_horse:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["carnival", "horse", "photo", "carousel", "horse", "amusement", "park", "ride", "entertainment", "park", "fair"],
+ "moji": "🎠"
+ },
+ "cartridge": {
+ "unicode": "1F5AD",
+ "unicode_alternates": [],
+ "name": "tape cartridge",
+ "shortname": ":cartridge:",
+ "category": "objects_symbols",
+ "aliases": [":tape_cartridge:"],
+ "aliases_ascii": [],
+ "keywords": ["oldschool", "save", "technology", "disk", "storage", "information", "computer", "drive", "megabyte"]
+ },
+ "cat": {
+ "unicode": "1F431",
+ "unicode_alternates": [],
+ "name": "cat face",
+ "shortname": ":cat:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "meow"],
+ "moji": "🐱"
+ },
+ "cat2": {
+ "unicode": "1F408",
+ "unicode_alternates": [],
+ "name": "cat",
+ "shortname": ":cat2:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "meow", "pet", "cat", "kitten", "meow"],
+ "moji": "🐈"
+ },
+ "celtic_cross": {
+ "unicode": "1F548",
+ "unicode_alternates": [],
+ "name": "celtic cross",
+ "shortname": ":celtic_cross:",
+ "category": "objects_symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["religion", "symbol"]
+ },
+ "chart": {
+ "unicode": "1F4B9",
+ "unicode_alternates": [],
+ "name": "chart with upwards trend and yen sign",
+ "shortname": ":chart:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["graph", "green-square"],
+ "moji": "💹"
+ },
+ "chart_with_downwards_trend": {
+ "unicode": "1F4C9",
+ "unicode_alternates": [],
+ "name": "chart with downwards trend",
+ "shortname": ":chart_with_downwards_trend:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["graph"],
+ "moji": "📉"
+ },
+ "chart_with_upwards_trend": {
+ "unicode": "1F4C8",
+ "unicode_alternates": [],
+ "name": "chart with upwards trend",
+ "shortname": ":chart_with_upwards_trend:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["graph"],
+ "moji": "📈"
+ },
+ "checkered_flag": {
+ "unicode": "1F3C1",
+ "unicode_alternates": [],
+ "name": "chequered flag",
+ "shortname": ":checkered_flag:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["contest", "finishline", "gokart", "rase", "checkered", "chequred", "race", "flag", "finish", "complete", "end"],
+ "moji": "🏁"
+ },
+ "cherries": {
+ "unicode": "1F352",
+ "unicode_alternates": [],
+ "name": "cherries",
+ "shortname": ":cherries:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["food", "fruit", "cherry", "cherries", "tree", "fruit", "pit"],
+ "moji": "🍒"
+ },
+ "cherry_blossom": {
+ "unicode": "1F338",
+ "unicode_alternates": [],
+ "name": "cherry blossom",
+ "shortname": ":cherry_blossom:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["flower", "nature", "plant", "cherry", "blossom", "tree", "flower"],
+ "moji": "🌸"
+ },
+ "chestnut": {
+ "unicode": "1F330",
+ "unicode_alternates": [],
+ "name": "chestnut",
+ "shortname": ":chestnut:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["food", "squirrel", "chestnut", "roasted", "food", "tree"],
+ "moji": "🌰"
+ },
+ "chicken": {
+ "unicode": "1F414",
+ "unicode_alternates": [],
+ "name": "chicken",
+ "shortname": ":chicken:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "cluck", "chicken", "hen", "poultry", "livestock"],
+ "moji": "🐔"
+ },
+ "children_crossing": {
+ "unicode": "1F6B8",
+ "unicode_alternates": [],
+ "name": "children crossing",
+ "shortname": ":children_crossing:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["school", "children", "kids", "caution", "crossing", "street", "crosswalk", "slow"],
+ "moji": "🚸"
+ },
+ "chipmunk": {
+ "unicode": "1F43F",
+ "unicode_alternates": [],
+ "name": "chipmunk",
+ "shortname": ":chipmunk:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "nature"]
+ },
+ "chocolate_bar": {
+ "unicode": "1F36B",
+ "unicode_alternates": [],
+ "name": "chocolate bar",
+ "shortname": ":chocolate_bar:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["desert", "food", "snack", "chocolate", "bar", "candy", "coca", "hershey&#039;s"],
+ "moji": "🍫"
+ },
+ "christmas_tree": {
+ "unicode": "1F384",
+ "unicode_alternates": [],
+ "name": "christmas tree",
+ "shortname": ":christmas_tree:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["celebration", "december", "festival", "vacation", "xmas", "christmas", "xmas", "santa", "holiday", "winter", "december", "santa", "evergreen", "ornaments", "jesus", "gifts", "presents"],
+ "moji": "🎄"
+ },
+ "church": {
+ "unicode": "26EA",
+ "unicode_alternates": ["26EA-FE0F"],
+ "name": "church",
+ "shortname": ":church:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["building", "christ", "religion"],
+ "moji": "⛪"
+ },
+ "cinema": {
+ "unicode": "1F3A6",
+ "unicode_alternates": [],
+ "name": "cinema",
+ "shortname": ":cinema:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blue-square", "film", "movie", "record", "cinema", "movie", "theater", "motion", "picture"],
+ "moji": "🎦"
+ },
+ "circus_tent": {
+ "unicode": "1F3AA",
+ "unicode_alternates": [],
+ "name": "circus tent",
+ "shortname": ":circus_tent:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["carnival", "festival", "party", "circus", "tent", "event", "carnival", "big", "top", "canvas"],
+ "moji": "🎪"
+ },
+ "city_dusk": {
+ "unicode": "1F306",
+ "unicode_alternates": [],
+ "name": "cityscape at dusk",
+ "shortname": ":city_dusk:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["photo", "city", "scape", "sunset", "dusk", "lights", "evening", "metropolitan", "night", "dark"],
+ "moji": "🌆"
+ },
+ "city_sunset": {
+ "unicode": "1F307",
+ "unicode_alternates": [],
+ "name": "sunset over buildings",
+ "shortname": ":city_sunset:",
+ "category": "places",
+ "aliases": [":city_sunrise:"],
+ "aliases_ascii": [],
+ "keywords": ["photo", "city", "scape", "sunrise", "dawn", "light", "morning", "metropolitan", "rise", "sun"],
+ "moji": "🌇"
+ },
+ "cityscape": {
+ "unicode": "1F3D9",
+ "unicode_alternates": [],
+ "name": "cityscape",
+ "shortname": ":cityscape:",
+ "category": "travel_places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["skyscraper", "city", "view", "lights", "buiildings", "metropolis"]
+ },
+ "clap": {
+ "unicode": "1F44F",
+ "unicode_alternates": [],
+ "name": "clapping hands sign",
+ "shortname": ":clap:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["applause", "congrats", "hands", "praise", "clapping", "appreciation", "approval", "sound", "encouragement", "enthusiasm"],
+ "moji": "👏"
+ },
+ "clapper": {
+ "unicode": "1F3AC",
+ "unicode_alternates": [],
+ "name": "clapper board",
+ "shortname": ":clapper:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["film", "movie", "record", "clapper", "board", "clapboard", "movie", "film", "take"],
+ "moji": "🎬"
+ },
+ "classical_building": {
+ "unicode": "1F3DB",
+ "unicode_alternates": [],
+ "name": "classical building",
+ "shortname": ":classical_building:",
+ "category": "travel_places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["government", "architecture", "history", "iconic", "genre"]
+ },
+ "clipboard": {
+ "unicode": "1F4CB",
+ "unicode_alternates": [],
+ "name": "clipboard",
+ "shortname": ":clipboard:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["documents", "stationery"],
+ "moji": "📋"
+ },
+ "clock": {
+ "unicode": "1F570",
+ "unicode_alternates": [],
+ "name": "mantlepiece clock",
+ "shortname": ":clock:",
+ "category": "objects_symbols",
+ "aliases": [":mantlepiece_clock:"],
+ "aliases_ascii": [],
+ "keywords": ["time"]
+ },
+ "clock1": {
+ "unicode": "1F550",
+ "unicode_alternates": [],
+ "name": "clock face one oclock",
+ "shortname": ":clock1:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["clock", "time"],
+ "moji": "🕐"
+ },
+ "clock10": {
+ "unicode": "1F559",
+ "unicode_alternates": [],
+ "name": "clock face ten oclock",
+ "shortname": ":clock10:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["clock", "time"],
+ "moji": "🕙"
+ },
+ "clock1030": {
+ "unicode": "1F565",
+ "unicode_alternates": [],
+ "name": "clock face ten-thirty",
+ "shortname": ":clock1030:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["clock", "time"],
+ "moji": "🕥"
+ },
+ "clock11": {
+ "unicode": "1F55A",
+ "unicode_alternates": [],
+ "name": "clock face eleven oclock",
+ "shortname": ":clock11:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["clock", "time"],
+ "moji": "🕚"
+ },
+ "clock1130": {
+ "unicode": "1F566",
+ "unicode_alternates": [],
+ "name": "clock face eleven-thirty",
+ "shortname": ":clock1130:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["clock", "time"],
+ "moji": "🕦"
+ },
+ "clock12": {
+ "unicode": "1F55B",
+ "unicode_alternates": [],
+ "name": "clock face twelve oclock",
+ "shortname": ":clock12:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["clock", "time"],
+ "moji": "🕛"
+ },
+ "clock1230": {
+ "unicode": "1F567",
+ "unicode_alternates": [],
+ "name": "clock face twelve-thirty",
+ "shortname": ":clock1230:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["clock", "time"]
+ },
+ "clock130": {
+ "unicode": "1F55C",
+ "unicode_alternates": [],
+ "name": "clock face one-thirty",
+ "shortname": ":clock130:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["clock", "time"],
+ "moji": "🕜"
+ },
+ "clock2": {
+ "unicode": "1F551",
+ "unicode_alternates": [],
+ "name": "clock face two oclock",
+ "shortname": ":clock2:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["clock", "time"],
+ "moji": "🕑"
+ },
+ "clock230": {
+ "unicode": "1F55D",
+ "unicode_alternates": [],
+ "name": "clock face two-thirty",
+ "shortname": ":clock230:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["clock", "time"],
+ "moji": "🕝"
+ },
+ "clock3": {
+ "unicode": "1F552",
+ "unicode_alternates": [],
+ "name": "clock face three oclock",
+ "shortname": ":clock3:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["clock", "time"],
+ "moji": "🕒"
+ },
+ "clock330": {
+ "unicode": "1F55E",
+ "unicode_alternates": [],
+ "name": "clock face three-thirty",
+ "shortname": ":clock330:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["clock", "time"],
+ "moji": "🕞"
+ },
+ "clock4": {
+ "unicode": "1F553",
+ "unicode_alternates": [],
+ "name": "clock face four oclock",
+ "shortname": ":clock4:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["clock", "time"],
+ "moji": "🕓"
+ },
+ "clock430": {
+ "unicode": "1F55F",
+ "unicode_alternates": [],
+ "name": "clock face four-thirty",
+ "shortname": ":clock430:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["clock", "time"],
+ "moji": "🕟"
+ },
+ "clock5": {
+ "unicode": "1F554",
+ "unicode_alternates": [],
+ "name": "clock face five oclock",
+ "shortname": ":clock5:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["clock", "time"],
+ "moji": "🕔"
+ },
+ "clock530": {
+ "unicode": "1F560",
+ "unicode_alternates": [],
+ "name": "clock face five-thirty",
+ "shortname": ":clock530:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["clock", "time"],
+ "moji": "🕠"
+ },
+ "clock6": {
+ "unicode": "1F555",
+ "unicode_alternates": [],
+ "name": "clock face six oclock",
+ "shortname": ":clock6:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["clock", "time"],
+ "moji": "🕕"
+ },
+ "clock630": {
+ "unicode": "1F561",
+ "unicode_alternates": [],
+ "name": "clock face six-thirty",
+ "shortname": ":clock630:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["clock", "time"],
+ "moji": "🕡"
+ },
+ "clock7": {
+ "unicode": "1F556",
+ "unicode_alternates": [],
+ "name": "clock face seven oclock",
+ "shortname": ":clock7:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["clock", "time"],
+ "moji": "🕖"
+ },
+ "clock730": {
+ "unicode": "1F562",
+ "unicode_alternates": [],
+ "name": "clock face seven-thirty",
+ "shortname": ":clock730:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["clock", "time"],
+ "moji": "🕢"
+ },
+ "clock8": {
+ "unicode": "1F557",
+ "unicode_alternates": [],
+ "name": "clock face eight oclock",
+ "shortname": ":clock8:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["clock", "time"],
+ "moji": "🕗"
+ },
+ "clock830": {
+ "unicode": "1F563",
+ "unicode_alternates": [],
+ "name": "clock face eight-thirty",
+ "shortname": ":clock830:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["clock", "time"],
+ "moji": "🕣"
+ },
+ "clock9": {
+ "unicode": "1F558",
+ "unicode_alternates": [],
+ "name": "clock face nine oclock",
+ "shortname": ":clock9:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["clock", "time"],
+ "moji": "🕘"
+ },
+ "clock930": {
+ "unicode": "1F564",
+ "unicode_alternates": [],
+ "name": "clock face nine-thirty",
+ "shortname": ":clock930:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["clock", "time"],
+ "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": [],
+ "name": "closed book",
+ "shortname": ":closed_book:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["knowledge", "library", "read"],
+ "moji": "📕"
+ },
+ "closed_lock_with_key": {
+ "unicode": "1F510",
+ "unicode_alternates": [],
+ "name": "closed lock with key",
+ "shortname": ":closed_lock_with_key:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["privacy", "security"],
+ "moji": "🔐"
+ },
+ "closed_umbrella": {
+ "unicode": "1F302",
+ "unicode_alternates": [],
+ "name": "closed umbrella",
+ "shortname": ":closed_umbrella:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["drizzle", "rain", "weather", "umbrella", "closed", "rain", "moisture", "protection", "sun", "ultraviolet", "uv"],
+ "moji": "🌂"
+ },
+ "cloud": {
+ "unicode": "2601",
+ "unicode_alternates": ["2601-FE0F"],
+ "name": "cloud",
+ "shortname": ":cloud:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["sky", "weather"],
+ "moji": "☁"
+ },
+ "cloud_lightning": {
+ "unicode": "1F329",
+ "unicode_alternates": [],
+ "name": "cloud with lightning",
+ "shortname": ":cloud_lightning:",
+ "category": "nature",
+ "aliases": [":cloud_with_lightning:"],
+ "aliases_ascii": [],
+ "keywords": ["weather", "thunder"]
+ },
+ "cloud_rain": {
+ "unicode": "1F327",
+ "unicode_alternates": [],
+ "name": "cloud with rain",
+ "shortname": ":cloud_rain:",
+ "category": "nature",
+ "aliases": [":cloud_with_rain:"],
+ "aliases_ascii": [],
+ "keywords": ["weather", "wet"]
+ },
+ "cloud_snow": {
+ "unicode": "1F328",
+ "unicode_alternates": [],
+ "name": "cloud with snow",
+ "shortname": ":cloud_snow:",
+ "category": "nature",
+ "aliases": [":cloud_with_snow:"],
+ "aliases_ascii": [],
+ "keywords": ["weather", "cold"]
+ },
+ "cloud_tornado": {
+ "unicode": "1F32A",
+ "unicode_alternates": [],
+ "name": "cloud with tornado",
+ "shortname": ":cloud_tornado:",
+ "category": "nature",
+ "aliases": [":cloud_with_tornado:"],
+ "aliases_ascii": [],
+ "keywords": ["weather", "destruction", "funnel"]
+ },
+ "clubs": {
+ "unicode": "2663",
+ "unicode_alternates": ["2663-FE0F"],
+ "name": "black club suit",
+ "shortname": ":clubs:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["cards", "poker"],
+ "moji": "♣"
+ },
+ "cocktail": {
+ "unicode": "1F378",
+ "unicode_alternates": [],
+ "name": "cocktail glass",
+ "shortname": ":cocktail:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["alcohol", "beverage", "drink", "drunk", "cocktail", "mixed", "drink", "alcohol", "glass", "martini", "bar"],
+ "moji": "🍸"
+ },
+ "coffee": {
+ "unicode": "2615",
+ "unicode_alternates": ["2615-FE0F"],
+ "name": "hot beverage",
+ "shortname": ":coffee:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["beverage", "cafe", "drink", "espresso"],
+ "moji": "☕"
+ },
+ "cold_sweat": {
+ "unicode": "1F630",
+ "unicode_alternates": [],
+ "name": "face with open mouth and cold sweat",
+ "shortname": ":cold_sweat:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["face", "nervous", "sweat", "exasperated", "frustrated"],
+ "moji": "😰"
+ },
+ "compression": {
+ "unicode": "1F5DC",
+ "unicode_alternates": [],
+ "name": "compression",
+ "shortname": ":compression:",
+ "category": "objects_symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["reduce"]
+ },
+ "computer": {
+ "unicode": "1F4BB",
+ "unicode_alternates": [],
+ "name": "personal computer",
+ "shortname": ":computer:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["laptop", "tech"],
+ "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": [],
+ "name": "confetti ball",
+ "shortname": ":confetti_ball:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["festival", "party", "party", "congratulations", "confetti", "ball", "celebrate", "win", "birthday", "new years", "wedding"],
+ "moji": "🎊"
+ },
+ "confounded": {
+ "unicode": "1F616",
+ "unicode_alternates": [],
+ "name": "confounded face",
+ "shortname": ":confounded:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["confused", "face", "sick", "unwell", "confound", "amaze", "perplex", "puzzle", "mystify"],
+ "moji": "😖"
+ },
+ "confused": {
+ "unicode": "1F615",
+ "unicode_alternates": [],
+ "name": "confused face",
+ "shortname": ":confused:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [">:\\", ">:/", ":-/", ":-.", ":/", ":\\", "=/", "=\\", ":L", "=L"],
+ "keywords": ["confused", "confuse", "daze", "perplex", "puzzle", "indifference", "skeptical", "undecided", "uneasy", "hesitant"],
+ "moji": "😕"
+ },
+ "congratulations": {
+ "unicode": "3297",
+ "unicode_alternates": ["3297-FE0F"],
+ "name": "circled ideograph congratulation",
+ "shortname": ":congratulations:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["chinese", "japanese", "kanji"],
+ "moji": "㊗"
+ },
+ "construction": {
+ "unicode": "1F6A7",
+ "unicode_alternates": [],
+ "name": "construction sign",
+ "shortname": ":construction:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["caution", "progress", "wip"],
+ "moji": "🚧"
+ },
+ "construction_worker": {
+ "unicode": "1F477",
+ "unicode_alternates": [],
+ "name": "construction worker",
+ "shortname": ":construction_worker:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["human", "male", "man", "wip"],
+ "moji": "👷"
+ },
+ "control_knobs": {
+ "unicode": "1F39B",
+ "unicode_alternates": [],
+ "name": "control knobs",
+ "shortname": ":control_knobs:",
+ "category": "objects_symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["dial"]
+ },
+ "contruction_site": {
+ "unicode": "1F3D7",
+ "unicode_alternates": [],
+ "name": "building construction",
+ "shortname": ":contruction_site:",
+ "category": "travel_places",
+ "aliases": [":building_construction:"],
+ "aliases_ascii": [],
+ "keywords": ["site", "work"]
+ },
+ "convenience_store": {
+ "unicode": "1F3EA",
+ "unicode_alternates": [],
+ "name": "convenience store",
+ "shortname": ":convenience_store:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["building"],
+ "moji": "🏪"
+ },
+ "cookie": {
+ "unicode": "1F36A",
+ "unicode_alternates": [],
+ "name": "cookie",
+ "shortname": ":cookie:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["chocolate", "food", "oreo", "snack", "cookie", "dessert", "biscuit", "sweet", "chocolate"],
+ "moji": "🍪"
+ },
+ "cool": {
+ "unicode": "1F192",
+ "unicode_alternates": [],
+ "name": "squared cool",
+ "shortname": ":cool:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blue-square", "words"],
+ "moji": "🆒"
+ },
+ "cop": {
+ "unicode": "1F46E",
+ "unicode_alternates": [],
+ "name": "police officer",
+ "shortname": ":cop:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["arrest", "enforcement", "law", "man", "police"],
+ "moji": "👮"
+ },
+ "copyright": {
+ "moji": "©",
+ "unicode": "00A9",
+ "unicode_alternates": [],
+ "name": "copyright sign",
+ "shortname": ":copyright:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["ip", "license"]
+ },
+ "corn": {
+ "unicode": "1F33D",
+ "unicode_alternates": [],
+ "name": "ear of maize",
+ "shortname": ":corn:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["food", "plant", "vegetable", "corn", "maize", "food", "iowa", "kernel", "popcorn", "husk", "yellow", "stalk", "cob", "ear"],
+ "moji": "🌽"
+ },
+ "couch": {
+ "unicode": "1F6CB",
+ "unicode_alternates": [],
+ "name": "couch and lamp",
+ "shortname": ":couch:",
+ "category": "travel_places",
+ "aliases": [":couch_and_lamp:"],
+ "aliases_ascii": [],
+ "keywords": ["lounge", "sectional", "sofa", "loveseat", "leather", "microfiber", "sit", "relax"]
+ },
+ "couple": {
+ "unicode": "1F46B",
+ "unicode_alternates": [],
+ "name": "man and woman holding hands",
+ "shortname": ":couple:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["affection", "date", "dating", "human", "like", "love", "marriage", "people", "valentines"],
+ "moji": "👫"
+ },
+ "couple_mm": {
+ "unicode": "1F468-2764-1F468",
+ "unicode_alternates": ["1F468-200D-2764-FE0F-200D-1F468"],
+ "name": "couple (man,man)",
+ "shortname": ":couple_mm:",
+ "category": "people",
+ "aliases": [":couple_with_heart_mm:"],
+ "aliases_ascii": [],
+ "keywords": ["affection", "dating", "human", "like", "love", "marriage", "valentines"]
+ },
+ "couple_with_heart": {
+ "unicode": "1F491",
+ "unicode_alternates": [],
+ "name": "couple with heart",
+ "shortname": ":couple_with_heart:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["affection", "dating", "human", "like", "love", "marriage", "valentines"],
+ "moji": "💑"
+ },
+ "couple_ww": {
+ "unicode": "1F469-2764-1F469",
+ "unicode_alternates": ["1F469-200D-2764-FE0F-200D-1F469"],
+ "name": "couple (woman,woman)",
+ "shortname": ":couple_ww:",
+ "category": "people",
+ "aliases": [":couple_with_heart_ww:"],
+ "aliases_ascii": [],
+ "keywords": ["affection", "dating", "human", "like", "love", "marriage", "valentines"]
+ },
+ "couplekiss": {
+ "unicode": "1F48F",
+ "unicode_alternates": [],
+ "name": "kiss",
+ "shortname": ":couplekiss:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["dating", "like", "love", "marriage", "valentines"],
+ "moji": "💏"
+ },
+ "cow": {
+ "unicode": "1F42E",
+ "unicode_alternates": [],
+ "name": "cow face",
+ "shortname": ":cow:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "beef", "ox"],
+ "moji": "🐮"
+ },
+ "cow2": {
+ "unicode": "1F404",
+ "unicode_alternates": [],
+ "name": "cow",
+ "shortname": ":cow2:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "beef", "nature", "ox", "cow", "milk", "dairy", "beef", "bessie", "moo"],
+ "moji": "🐄"
+ },
+ "crayon": {
+ "unicode": "1F58D",
+ "unicode_alternates": [],
+ "name": "lower left crayon",
+ "shortname": ":crayon:",
+ "category": "objects_symbols",
+ "aliases": [":lower_left_crayon:"],
+ "aliases_ascii": [],
+ "keywords": ["write", "draw", "color", "wax"]
+ },
+ "credit_card": {
+ "unicode": "1F4B3",
+ "unicode_alternates": [],
+ "name": "credit card",
+ "shortname": ":credit_card:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["bill", "dollar", "money", "pay", "payment", "credit", "card", "loan", "purchase", "shopping", "mastercard", "visa", "american express", "wallet", "signature"],
+ "moji": "💳"
+ },
+ "crescent_moon": {
+ "unicode": "1F319",
+ "unicode_alternates": [],
+ "name": "crescent moon",
+ "shortname": ":crescent_moon:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["night", "moon", "crescent", "waxing", "sky", "night", "cheese", "phase"],
+ "moji": "🌙"
+ },
+ "crocodile": {
+ "unicode": "1F40A",
+ "unicode_alternates": [],
+ "name": "crocodile",
+ "shortname": ":crocodile:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "nature", "crocodile", "croc", "alligator", "gator", "cranky"],
+ "moji": "🐊"
+ },
+ "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"]
+ },
+ "crossed_flags": {
+ "unicode": "1F38C",
+ "unicode_alternates": [],
+ "name": "crossed flags",
+ "shortname": ":crossed_flags:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["japan"],
+ "moji": "🎌"
+ },
+ "crown": {
+ "unicode": "1F451",
+ "unicode_alternates": [],
+ "name": "crown",
+ "shortname": ":crown:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["king", "kod", "leader", "royalty"],
+ "moji": "👑"
+ },
+ "cruise_ship": {
+ "unicode": "1F6F3",
+ "unicode_alternates": [],
+ "name": "passenger ship",
+ "shortname": ":cruise_ship:",
+ "category": "travel_places",
+ "aliases": [":passenger_ship:"],
+ "aliases_ascii": [],
+ "keywords": ["titanic", "transportation", "boat"]
+ },
+ "cry": {
+ "unicode": "1F622",
+ "unicode_alternates": [],
+ "name": "crying face",
+ "shortname": ":cry:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [":'(", ":'-(", ";(", ";-("],
+ "keywords": ["face", "sad", "sad", "cry", "tear", "weep", "tears"],
+ "moji": "😢"
+ },
+ "crying_cat_face": {
+ "unicode": "1F63F",
+ "unicode_alternates": [],
+ "name": "crying cat face",
+ "shortname": ":crying_cat_face:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "cats", "sad", "tears", "weep", "cry", "cat", "sob", "tears", "sad", "melancholy", "morn", "somber", "hurt"],
+ "moji": "😿"
+ },
+ "crystal_ball": {
+ "unicode": "1F52E",
+ "unicode_alternates": [],
+ "name": "crystal ball",
+ "shortname": ":crystal_ball:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["disco", "party"],
+ "moji": "🔮"
+ },
+ "cupid": {
+ "unicode": "1F498",
+ "unicode_alternates": [],
+ "name": "heart with arrow",
+ "shortname": ":cupid:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["affection", "heart", "like", "love", "valentines"],
+ "moji": "💘"
+ },
+ "curly_loop": {
+ "unicode": "27B0",
+ "unicode_alternates": [],
+ "name": "curly loop",
+ "shortname": ":curly_loop:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["scribble"],
+ "moji": "➰"
+ },
+ "currency_exchange": {
+ "unicode": "1F4B1",
+ "unicode_alternates": [],
+ "name": "currency exchange",
+ "shortname": ":currency_exchange:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["dollar", "money", "travel"],
+ "moji": "💱"
+ },
+ "curry": {
+ "unicode": "1F35B",
+ "unicode_alternates": [],
+ "name": "curry and rice",
+ "shortname": ":curry:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["food", "hot", "indian", "spicy", "curry", "spice", "flavor", "food", "meal"],
+ "moji": "🍛"
+ },
+ "custard": {
+ "unicode": "1F36E",
+ "unicode_alternates": [],
+ "name": "custard",
+ "shortname": ":custard:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["desert", "food", "custard", "cream", "rich", "butter", "dessert", "crème", "brûlée", "french"],
+ "moji": "🍮"
+ },
+ "customs": {
+ "unicode": "1F6C3",
+ "unicode_alternates": [],
+ "name": "customs",
+ "shortname": ":customs:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["border", "passport", "customs", "travel", "foreign", "goods", "check", "authority", "government"],
+ "moji": "🛃"
+ },
+ "cyclone": {
+ "moji": "🌀",
+ "unicode": "1F300",
+ "unicode_alternates": [],
+ "name": "cyclone",
+ "shortname": ":cyclone:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blue", "cloud", "swirl", "weather", "cyclone", "hurricane", "typhoon", "storm", "ocean"]
+ },
+ "dagger": {
+ "unicode": "1F5E1",
+ "unicode_alternates": [],
+ "name": "dagger knife",
+ "shortname": ":dagger:",
+ "category": "objects_symbols",
+ "aliases": [":dagger_knife:"],
+ "aliases_ascii": [],
+ "keywords": ["blade", "knife"]
+ },
+ "dancer": {
+ "unicode": "1F483",
+ "unicode_alternates": [],
+ "name": "dancer",
+ "shortname": ":dancer:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["female", "fun", "girl", "woman", "dance", "dancer", "dress", "fancy", "boogy", "party", "celebrate", "ballet", "tango", "cha cha", "music"],
+ "moji": "💃"
+ },
+ "dancers": {
+ "unicode": "1F46F",
+ "unicode_alternates": [],
+ "name": "woman with bunny ears",
+ "shortname": ":dancers:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["bunny", "female", "girls", "women", "dancing", "dancers", "showgirl", "playboy", "costume", "bunny", "cancan"],
+ "moji": "👯"
+ },
+ "dango": {
+ "unicode": "1F361",
+ "unicode_alternates": [],
+ "name": "dango",
+ "shortname": ":dango:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["food", "dango", "japanese", "dumpling", "mochi", "balls", "skewer"],
+ "moji": "🍡"
+ },
+ "dark_sunglasses": {
+ "unicode": "1F576",
+ "unicode_alternates": [],
+ "name": "dark sunglasses",
+ "shortname": ":dark_sunglasses:",
+ "category": "objects_symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["shades", "eyes"]
+ },
+ "dart": {
+ "unicode": "1F3AF",
+ "unicode_alternates": [],
+ "name": "direct hit",
+ "shortname": ":dart:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["bar", "game", "direct", "hit", "bullseye", "dart", "archery", "game", "fletching", "arrow", "sport"],
+ "moji": "🎯"
+ },
+ "dash": {
+ "unicode": "1F4A8",
+ "unicode_alternates": [],
+ "name": "dash symbol",
+ "shortname": ":dash:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["air", "fast", "shoo", "wind"],
+ "moji": "💨"
+ },
+ "date": {
+ "unicode": "1F4C5",
+ "unicode_alternates": [],
+ "name": "calendar",
+ "shortname": ":date:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["calendar", "schedule"],
+ "moji": "📅"
+ },
+ "deciduous_tree": {
+ "unicode": "1F333",
+ "unicode_alternates": [],
+ "name": "deciduous tree",
+ "shortname": ":deciduous_tree:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["nature", "plant", "deciduous", "tree", "leaves", "fall", "color"],
+ "moji": "🌳"
+ },
+ "department_store": {
+ "unicode": "1F3EC",
+ "unicode_alternates": [],
+ "name": "department store",
+ "shortname": ":department_store:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["building", "mall", "shopping", "department", "store", "retail", "sale", "merchandise"],
+ "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",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["hot", "dry", "sandy", "cactus", "sunny", "barren"]
+ },
+ "desktop": {
+ "unicode": "1F5A5",
+ "unicode_alternates": [],
+ "name": "desktop computer",
+ "shortname": ":desktop:",
+ "category": "objects_symbols",
+ "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"]
+ },
+ "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",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["diamond", "cute", "cuteness", "kawaii", "japanese", "glyph", "adorable"],
+ "moji": "💠"
+ },
+ "diamonds": {
+ "unicode": "2666",
+ "unicode_alternates": ["2666-FE0F"],
+ "name": "black diamond suit",
+ "shortname": ":diamonds:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["cards", "poker"],
+ "moji": "♦"
+ },
+ "disappointed": {
+ "unicode": "1F61E",
+ "unicode_alternates": [],
+ "name": "disappointed face",
+ "shortname": ":disappointed:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [">:[", ":-(", ":(", ":-[", ":[", "=("],
+ "keywords": ["disappointed", "disappoint", "frown", "depressed", "discouraged", "face", "sad", "upset"],
+ "moji": "😞"
+ },
+ "disappointed_relieved": {
+ "unicode": "1F625",
+ "unicode_alternates": [],
+ "name": "disappointed but relieved face",
+ "shortname": ":disappointed_relieved:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["face", "nervous", "phew", "sweat", "disappoint", "relief"],
+ "moji": "😥"
+ },
+ "dividers": {
+ "unicode": "1F5C2",
+ "unicode_alternates": [],
+ "name": "card index dividers",
+ "shortname": ":dividers:",
+ "category": "objects_symbols",
+ "aliases": [":card_index_dividers:"],
+ "aliases_ascii": [],
+ "keywords": ["stationery", "rolodex"]
+ },
+ "dizzy": {
+ "unicode": "1F4AB",
+ "unicode_alternates": [],
+ "name": "dizzy symbol",
+ "shortname": ":dizzy:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["shoot", "sparkle", "star", "dizzy", "drunk", "sick", "intoxicated", "squeans", "starburst", "star"],
+ "moji": "💫"
+ },
+ "dizzy_face": {
+ "unicode": "1F635",
+ "unicode_alternates": [],
+ "name": "dizzy face",
+ "shortname": ":dizzy_face:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": ["#-)", "#)", "%-)", "%)", "X)", "X-)"],
+ "keywords": ["dizzy", "drunk", "inebriated", "face", "spent", "unconscious", "xox"],
+ "moji": "😵"
+ },
+ "do_not_litter": {
+ "unicode": "1F6AF",
+ "unicode_alternates": [],
+ "name": "do not litter symbol",
+ "shortname": ":do_not_litter:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["bin", "garbage", "trash", "litter", "garbage", "waste", "no", "can", "trash"],
+ "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": [],
+ "name": "dog face",
+ "shortname": ":dog:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "friend", "nature", "woof"],
+ "moji": "🐶"
+ },
+ "dog2": {
+ "unicode": "1F415",
+ "unicode_alternates": [],
+ "name": "dog",
+ "shortname": ":dog2:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "doge", "friend", "nature", "pet", "dog", "puppy", "pet", "friend", "woof", "bark", "fido"],
+ "moji": "🐕"
+ },
+ "dollar": {
+ "unicode": "1F4B5",
+ "unicode_alternates": [],
+ "name": "banknote with dollar sign",
+ "shortname": ":dollar:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["bill", "currency", "money", "dollar", "united states", "canada", "australia", "banknote", "money", "currency", "paper", "cash", "bills"],
+ "moji": "💵"
+ },
+ "dolls": {
+ "unicode": "1F38E",
+ "unicode_alternates": [],
+ "name": "japanese dolls",
+ "shortname": ":dolls:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["japanese", "kimono", "toy", "dolls", "japan", "japanese", "day", "girls", "emperor", "empress", "pray", "blessing", "imperial", "family", "royal"],
+ "moji": "🎎"
+ },
+ "dolphin": {
+ "unicode": "1F42C",
+ "unicode_alternates": [],
+ "name": "dolphin",
+ "shortname": ":dolphin:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "fins", "fish", "flipper", "nature", "ocean", "sea"],
+ "moji": "🐬"
+ },
+ "door": {
+ "unicode": "1F6AA",
+ "unicode_alternates": [],
+ "name": "door",
+ "shortname": ":door:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["entry", "exit", "house", "door", "doorway", "entrance", "enter", "exit", "entry"],
+ "moji": "🚪"
+ },
+ "doughnut": {
+ "unicode": "1F369",
+ "unicode_alternates": [],
+ "name": "doughnut",
+ "shortname": ":doughnut:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["desert", "food", "snack", "sweet", "doughnut", "donut", "pastry", "fried", "dessert", "breakfast", "police", "homer", "sweet"],
+ "moji": "🍩"
+ },
+ "dove": {
+ "unicode": "1F54A",
+ "unicode_alternates": [],
+ "name": "dove of peace",
+ "shortname": ":dove:",
+ "category": "objects_symbols",
+ "aliases": [":dove_of_peace:"],
+ "aliases_ascii": [],
+ "keywords": ["symbol", "bird"]
+ },
+ "dragon": {
+ "unicode": "1F409",
+ "unicode_alternates": [],
+ "name": "dragon",
+ "shortname": ":dragon:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "chinese", "green", "myth", "nature", "dragon", "fire", "legendary", "myth"],
+ "moji": "🐉"
+ },
+ "dragon_face": {
+ "unicode": "1F432",
+ "unicode_alternates": [],
+ "name": "dragon face",
+ "shortname": ":dragon_face:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "chinese", "green", "myth", "nature", "dragon", "head", "fire", "legendary", "myth"],
+ "moji": "🐲"
+ },
+ "dress": {
+ "unicode": "1F457",
+ "unicode_alternates": [],
+ "name": "dress",
+ "shortname": ":dress:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["clothes", "fashion"],
+ "moji": "👗"
+ },
+ "dromedary_camel": {
+ "unicode": "1F42A",
+ "unicode_alternates": [],
+ "name": "dromedary camel",
+ "shortname": ":dromedary_camel:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "desert", "hot", "dromedary", "camel", "hump", "desert", "middle east", "heat", "hot", "water", "hump day", "wednesday", "sex"],
+ "moji": "🐪"
+ },
+ "droplet": {
+ "unicode": "1F4A7",
+ "unicode_alternates": [],
+ "name": "droplet",
+ "shortname": ":droplet:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["drip", "faucet", "water", "drop", "droplet", "h20", "water", "aqua", "tear", "sweat", "rain", "moisture", "wet", "moist", "spit"],
+ "moji": "💧"
+ },
+ "dvd": {
+ "unicode": "1F4C0",
+ "unicode_alternates": [],
+ "name": "dvd",
+ "shortname": ":dvd:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["cd", "disc", "disk"],
+ "moji": "📀"
+ },
+ "e-mail": {
+ "unicode": "1F4E7",
+ "unicode_alternates": [],
+ "name": "e-mail symbol",
+ "shortname": ":e-mail:",
+ "category": "objects",
+ "aliases": [":email:"],
+ "aliases_ascii": [],
+ "keywords": ["communication", "inbox"],
+ "moji": "📧"
+ },
+ "ear": {
+ "unicode": "1F442",
+ "unicode_alternates": [],
+ "name": "ear",
+ "shortname": ":ear:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["face", "hear", "listen", "sound"],
+ "moji": "👂"
+ },
+ "ear_of_rice": {
+ "unicode": "1F33E",
+ "unicode_alternates": [],
+ "name": "ear of rice",
+ "shortname": ":ear_of_rice:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["nature", "plant", "ear", "rice", "food", "plant", "seed"],
+ "moji": "🌾"
+ },
+ "earth_africa": {
+ "unicode": "1F30D",
+ "unicode_alternates": [],
+ "name": "earth globe europe-africa",
+ "shortname": ":earth_africa:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["globe", "international", "world", "earth", "globe", "space", "planet", "africa", "europe", "home"],
+ "moji": "🌍"
+ },
+ "earth_americas": {
+ "unicode": "1F30E",
+ "unicode_alternates": [],
+ "name": "earth globe americas",
+ "shortname": ":earth_americas:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["USA", "globe", "international", "world", "earth", "globe", "space", "planet", "north", "south", "america", "americas", "home"],
+ "moji": "🌎"
+ },
+ "earth_asia": {
+ "unicode": "1F30F",
+ "unicode_alternates": [],
+ "name": "earth globe asia-australia",
+ "shortname": ":earth_asia:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["east", "globe", "international", "world", "earth", "globe", "space", "planet", "asia", "australia", "home"],
+ "moji": "🌏"
+ },
+ "egg": {
+ "unicode": "1F373",
+ "unicode_alternates": [],
+ "name": "cooking",
+ "shortname": ":egg:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["breakfast", "food", "egg", "fry", "pan", "flat", "cook", "frying", "cooking", "utensil"],
+ "moji": "🍳"
+ },
+ "eggplant": {
+ "unicode": "1F346",
+ "unicode_alternates": [],
+ "name": "aubergine",
+ "shortname": ":eggplant:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["aubergine", "food", "nature", "vegetable", "eggplant", "aubergine", "fruit", "purple", "penis"],
+ "moji": "🍆"
+ },
+ "eight": {
+ "moji": "8️⃣",
+ "unicode": "0038-20E3",
+ "unicode_alternates": ["0038-FE0F-20E3"],
+ "name": "digit eight",
+ "shortname": ":eight:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["8", "blue-square", "numbers"]
+ },
+ "eight_pointed_black_star": {
+ "unicode": "2734",
+ "unicode_alternates": ["2734-FE0F"],
+ "name": "eight pointed black star",
+ "shortname": ":eight_pointed_black_star:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "✴"
+ },
+ "eight_spoked_asterisk": {
+ "unicode": "2733",
+ "unicode_alternates": ["2733-FE0F"],
+ "name": "eight spoked asterisk",
+ "shortname": ":eight_spoked_asterisk:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["green-square", "sparkle", "star"],
+ "moji": "✳"
+ },
+ "electric_plug": {
+ "unicode": "1F50C",
+ "unicode_alternates": [],
+ "name": "electric plug",
+ "shortname": ":electric_plug:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["charger", "power"],
+ "moji": "🔌"
+ },
+ "elephant": {
+ "unicode": "1F418",
+ "unicode_alternates": [],
+ "name": "elephant",
+ "shortname": ":elephant:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "nature", "nose", "thailand"],
+ "moji": "🐘"
+ },
+ "end": {
+ "unicode": "1F51A",
+ "unicode_alternates": [],
+ "name": "end with leftwards arrow above",
+ "shortname": ":end:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["arrow", "words"],
+ "moji": "🔚"
+ },
+ "envelope": {
+ "unicode": "2709",
+ "unicode_alternates": ["2709-FE0F"],
+ "name": "envelope",
+ "shortname": ":envelope:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["communication", "letter", "mail", "postal"],
+ "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": [],
+ "name": "envelope with downwards arrow above",
+ "shortname": ":envelope_with_arrow:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["email"],
+ "moji": "📩"
+ },
+ "euro": {
+ "unicode": "1F4B6",
+ "unicode_alternates": [],
+ "name": "banknote with euro sign",
+ "shortname": ":euro:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["currency", "dollar", "money", "euro", "europe", "banknote", "money", "currency", "paper", "cash", "bills"],
+ "moji": "💶"
+ },
+ "european_castle": {
+ "unicode": "1F3F0",
+ "unicode_alternates": [],
+ "name": "european castle",
+ "shortname": ":european_castle:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["building", "history", "royalty", "castle", "european", "residence", "royalty", "disneyland", "disney", "fort", "fortified", "moat", "tower", "princess", "prince", "lord", "king", "queen", "fortress", "nobel", "stronghold"],
+ "moji": "🏰"
+ },
+ "european_post_office": {
+ "unicode": "1F3E4",
+ "unicode_alternates": [],
+ "name": "european post office",
+ "shortname": ":european_post_office:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["building"],
+ "moji": "🏤"
+ },
+ "evergreen_tree": {
+ "unicode": "1F332",
+ "unicode_alternates": [],
+ "name": "evergreen tree",
+ "shortname": ":evergreen_tree:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["nature", "plant", "evergreen", "tree", "needles", "christmas"],
+ "moji": "🌲"
+ },
+ "exclamation": {
+ "unicode": "2757",
+ "unicode_alternates": ["2757-FE0F"],
+ "name": "heavy exclamation mark symbol",
+ "shortname": ":exclamation:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["surprise"],
+ "moji": "❗"
+ },
+ "expressionless": {
+ "unicode": "1F611",
+ "unicode_alternates": [],
+ "name": "expressionless face",
+ "shortname": ":expressionless:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": ["-_-", "-__-", "-___-"],
+ "keywords": ["expressionless", "blank", "void", "vapid", "without expression", "face", "indifferent"],
+ "moji": "😑"
+ },
+ "eye": {
+ "unicode": "1F441",
+ "unicode_alternates": [],
+ "name": "eye",
+ "shortname": ":eye:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["look", "peek", "watch"]
+ },
+ "eyeglasses": {
+ "unicode": "1F453",
+ "unicode_alternates": [],
+ "name": "eyeglasses",
+ "shortname": ":eyeglasses:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["accessories", "eyesight", "fashion", "eyeglasses", "spectacles", "eye", "sight", "nearsightedness", "myopia", "farsightedness", "hyperopia", "frames", "vision", "see", "blurry", "contacts"],
+ "moji": "👓"
+ },
+ "eyes": {
+ "unicode": "1F440",
+ "unicode_alternates": [],
+ "name": "eyes",
+ "shortname": ":eyes:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["look", "peek", "stalk", "watch"],
+ "moji": "👀"
+ },
+ "factory": {
+ "unicode": "1F3ED",
+ "unicode_alternates": [],
+ "name": "factory",
+ "shortname": ":factory:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["building"],
+ "moji": "🏭"
+ },
+ "fallen_leaf": {
+ "unicode": "1F342",
+ "unicode_alternates": [],
+ "name": "fallen leaf",
+ "shortname": ":fallen_leaf:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["leaves", "nature", "plant", "vegetable", "leaf", "fall", "color", "deciduous", "autumn"],
+ "moji": "🍂"
+ },
+ "family": {
+ "unicode": "1F46A",
+ "unicode_alternates": [],
+ "name": "family",
+ "shortname": ":family:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["child", "dad", "father", "home", "mom", "mother", "parents", "family", "mother", "father", "child", "girl", "boy", "group", "unit"],
+ "moji": "👪"
+ },
+ "family_mmb": {
+ "unicode": "1F468-1F468-1F466",
+ "unicode_alternates": ["1F468-200D-1F468-200D-1F466"],
+ "name": "family (man,man,boy)",
+ "shortname": ":family_mmb:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["child", "dad", "father", "parents", "group", "unit", "gay", "homosexual", "man", "boy"]
+ },
+ "family_mmbb": {
+ "unicode": "1F468-1F468-1F466-1F466",
+ "unicode_alternates": ["1F468-200D-1F468-200D-1F466-200D-1F466"],
+ "name": "family (man,man,boy,boy)",
+ "shortname": ":family_mmbb:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["children", "dad", "father", "parents", "group", "unit", "gay", "homosexual", "man", "boy"]
+ },
+ "family_mmg": {
+ "unicode": "1F468-1F468-1F467",
+ "unicode_alternates": ["1F468-200D-1F468-200D-1F467"],
+ "name": "family (man,man,girl)",
+ "shortname": ":family_mmg:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["child", "dad", "father", "parents", "group", "unit", "gay", "homosexual", "man", "girl"]
+ },
+ "family_mmgb": {
+ "unicode": "1F468-1F468-1F467-1F466",
+ "unicode_alternates": ["1F468-200D-1F468-200D-1F467-200D-1F466"],
+ "name": "family (man,man,girl,boy)",
+ "shortname": ":family_mmgb:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["children", "dad", "father", "parents", "group", "unit", "gay", "homosexual", "man", "girl", "boy"]
+ },
+ "family_mmgg": {
+ "unicode": "1F468-1F468-1F467-1F467",
+ "unicode_alternates": ["1F468-200D-1F468-200D-1F467-200D-1F467"],
+ "name": "family (man,man,girl,girl)",
+ "shortname": ":family_mmgg:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["children", "dad", "father", "parents", "group", "unit", "gay", "homosexual", "man", "girl"]
+ },
+ "family_mwbb": {
+ "unicode": "1F468-1F469-1F466-1F466",
+ "unicode_alternates": ["1F468-200D-1F469-200D-1F466-200D-1F466"],
+ "name": "family (man,woman,boy,boy)",
+ "shortname": ":family_mwbb:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["dad", "father", "mom", "mother", "parents", "children", "boy", "group", "unit", "man", "woman"]
+ },
+ "family_mwg": {
+ "unicode": "1F468-1F469-1F467",
+ "unicode_alternates": ["1F468-200D-1F469-200D-1F467"],
+ "name": "family (man,woman,girl)",
+ "shortname": ":family_mwg:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["child", "dad", "father", "mom", "mother", "parents", "girl", "boy", "group", "unit", "man", "woman"]
+ },
+ "family_mwgb": {
+ "unicode": "1F468-1F469-1F467-1F466",
+ "unicode_alternates": ["1F468-200D-1F469-200D-1F467-200D-1F466"],
+ "name": "family (man,woman,girl,boy)",
+ "shortname": ":family_mwgb:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["dad", "father", "mom", "mother", "parents", "children", "girl", "boy", "group", "unit", "man", "woman"]
+ },
+ "family_mwgg": {
+ "unicode": "1F468-1F469-1F467-1F467",
+ "unicode_alternates": ["1F468-200D-1F469-200D-1F467-200D-1F467"],
+ "name": "family (man,woman,girl,girl)",
+ "shortname": ":family_mwgg:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["dad", "father", "mom", "mother", "parents", "children", "girl", "group", "unit", "man", "woman"]
+ },
+ "family_wwb": {
+ "unicode": "1F469-1F469-1F466",
+ "unicode_alternates": ["1F469-200D-1F469-200D-1F466"],
+ "name": "family (woman,woman,boy)",
+ "shortname": ":family_wwb:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["mom", "mother", "parents", "child", "boy", "group", "unit", "gay", "lesbian", "homosexual", "woman"]
+ },
+ "family_wwbb": {
+ "unicode": "1F469-1F469-1F466-1F466",
+ "unicode_alternates": ["1F469-200D-1F469-200D-1F466-200D-1F466"],
+ "name": "family (woman,woman,boy,boy)",
+ "shortname": ":family_wwbb:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["mom", "mother", "parents", "children", "group", "unit", "gay", "lesbian", "homosexual", "woman", "boy"]
+ },
+ "family_wwg": {
+ "unicode": "1F469-1F469-1F467",
+ "unicode_alternates": ["1F469-200D-1F469-200D-1F467"],
+ "name": "family (woman,woman,girl)",
+ "shortname": ":family_wwg:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["mom", "mother", "parents", "child", "woman", "girl", "group", "unit", "gay", "lesbian", "homosexual"]
+ },
+ "family_wwgb": {
+ "unicode": "1F469-1F469-1F467-1F466",
+ "unicode_alternates": ["1F469-200D-1F469-200D-1F467-200D-1F466"],
+ "name": "family (woman,woman,girl,boy)",
+ "shortname": ":family_wwgb:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["mom", "mother", "parents", "children", "group", "unit", "gay", "lesbian", "homosexual", "woman", "girl", "boy"]
+ },
+ "family_wwgg": {
+ "unicode": "1F469-1F469-1F467-1F467",
+ "unicode_alternates": ["1F469-200D-1F469-200D-1F467-200D-1F467"],
+ "name": "family (woman,woman,girl,girl)",
+ "shortname": ":family_wwgg:",
+ "category": "people",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["mom", "mother", "parents", "children", "group", "unit", "gay", "lesbian", "homosexual", "woman", "girl"]
+ },
+ "fast_forward": {
+ "unicode": "23E9",
+ "unicode_alternates": [],
+ "name": "black right-pointing double triangle",
+ "shortname": ":fast_forward:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blue-square"],
+ "moji": "⏩"
+ },
+ "fax": {
+ "unicode": "1F4E0",
+ "unicode_alternates": [],
+ "name": "fax machine",
+ "shortname": ":fax:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["communication", "technology"],
+ "moji": "📠"
+ },
+ "fearful": {
+ "unicode": "1F628",
+ "unicode_alternates": [],
+ "name": "fearful face",
+ "shortname": ":fearful:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["face", "nervous", "oops", "scared", "terrified", "fear", "fearful", "scared", "frightened"],
+ "moji": "😨"
+ },
+ "feet": {
+ "unicode": "1F43E",
+ "unicode_alternates": [],
+ "name": "paw prints",
+ "shortname": ":feet:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "cat", "dog", "footprints", "paw", "pet", "tracking", "paw", "prints", "mark", "imprints", "footsteps", "animal", "lion", "bear", "dog", "cat", "raccoon", "critter", "feet", "pawsteps"],
+ "moji": "🐾"
+ },
+ "ferris_wheel": {
+ "unicode": "1F3A1",
+ "unicode_alternates": [],
+ "name": "ferris wheel",
+ "shortname": ":ferris_wheel:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["carnival", "londoneye", "photo", "farris", "wheel", "amusement", "park", "fair", "ride", "entertainment"],
+ "moji": "🎡"
+ },
+ "file_cabinet": {
+ "unicode": "1F5C4",
+ "unicode_alternates": [],
+ "name": "file cabinet",
+ "shortname": ":file_cabinet:",
+ "category": "objects_symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["folders", "office", "documents", "storage"]
+ },
+ "file_folder": {
+ "unicode": "1F4C1",
+ "unicode_alternates": [],
+ "name": "file folder",
+ "shortname": ":file_folder:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["documents"],
+ "moji": "📁"
+ },
+ "film_frames": {
+ "unicode": "1F39E",
+ "unicode_alternates": [],
+ "name": "film frames",
+ "shortname": ":film_frames:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["movie", "record", "8mm", "16mm", "reel", "celluloid"]
+ },
+ "finger_pointing_down": {
+ "unicode": "1F597",
+ "unicode_alternates": [],
+ "name": "white down pointing left hand index",
+ "shortname": ":finger_pointing_down:",
+ "category": "people",
+ "aliases": [":white_down_pointing_left_hand_index:"],
+ "aliases_ascii": [],
+ "keywords": ["direction", "finger", "hand"]
+ },
+ "finger_pointing_down2": {
+ "unicode": "1F59F",
+ "unicode_alternates": [],
+ "name": "sideways white down pointing index",
+ "shortname": ":finger_pointing_down2:",
+ "category": "people",
+ "aliases": [":sideways_white_down_pointing_index:"],
+ "aliases_ascii": [],
+ "keywords": ["direction", "finger", "hand"]
+ },
+ "finger_pointing_left": {
+ "unicode": "1F598",
+ "unicode_alternates": [],
+ "name": "sideways white left pointing index",
+ "shortname": ":finger_pointing_left:",
+ "category": "people",
+ "aliases": [":sideways_white_left_pointing_index:"],
+ "aliases_ascii": [],
+ "keywords": ["direction", "finger", "hand"]
+ },
+ "finger_pointing_right": {
+ "unicode": "1F599",
+ "unicode_alternates": [],
+ "name": "sideways white right pointing index",
+ "shortname": ":finger_pointing_right:",
+ "category": "people",
+ "aliases": [":sideways_white_right_pointing_index:"],
+ "aliases_ascii": [],
+ "keywords": ["direction", "finger", "hand"]
+ },
+ "finger_pointing_up": {
+ "unicode": "1F59E",
+ "unicode_alternates": [],
+ "name": "sideways white up pointing index",
+ "shortname": ":finger_pointing_up:",
+ "category": "people",
+ "aliases": [":sideways_white_up_pointing_index:"],
+ "aliases_ascii": [],
+ "keywords": ["direction", "finger", "hand"]
+ },
+ "fire": {
+ "unicode": "1F525",
+ "unicode_alternates": [],
+ "name": "fire",
+ "shortname": ":fire:",
+ "category": "emoticons",
+ "aliases": [":flame:"],
+ "aliases_ascii": [],
+ "keywords": ["cook", "hot", "flame"],
+ "moji": "🔥"
+ },
+ "fire_engine": {
+ "unicode": "1F692",
+ "unicode_alternates": [],
+ "name": "fire engine",
+ "shortname": ":fire_engine:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["cars", "transportation", "vehicle", "fire", "fighter", "engine", "truck", "emergency", "medical"],
+ "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",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["carnival", "congratulations", "festival", "photo", "fireworks", "independence", "celebration", "explosion", "july", "4th", "rocket", "sky", "idea", "excitement"],
+ "moji": "🎆"
+ },
+ "first_quarter_moon": {
+ "unicode": "1F313",
+ "unicode_alternates": [],
+ "name": "first quarter moon symbol",
+ "shortname": ":first_quarter_moon:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["nature", "moon", "quarter", "first", "sky", "night", "cheese", "phase"],
+ "moji": "🌓"
+ },
+ "first_quarter_moon_with_face": {
+ "unicode": "1F31B",
+ "unicode_alternates": [],
+ "name": "first quarter moon with face",
+ "shortname": ":first_quarter_moon_with_face:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["nature", "moon", "first", "quarter", "anthropomorphic", "face", "sky", "night", "cheese", "phase"],
+ "moji": "🌛"
+ },
+ "fish": {
+ "unicode": "1F41F",
+ "unicode_alternates": [],
+ "name": "fish",
+ "shortname": ":fish:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "food", "nature"],
+ "moji": "🐟"
+ },
+ "fish_cake": {
+ "unicode": "1F365",
+ "unicode_alternates": [],
+ "name": "fish cake with swirl design",
+ "shortname": ":fish_cake:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["food", "fish", "cake", "kamboko", "swirl", "ramen", "noodles", "naruto"],
+ "moji": "🍥"
+ },
+ "fishing_pole_and_fish": {
+ "unicode": "1F3A3",
+ "unicode_alternates": [],
+ "name": "fishing pole and fish",
+ "shortname": ":fishing_pole_and_fish:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["food", "hobby", "fish", "fishing", "pole"],
+ "moji": "🎣"
+ },
+ "fist": {
+ "unicode": "270A",
+ "unicode_alternates": [],
+ "name": "raised fist",
+ "shortname": ":fist:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["fingers", "grasp", "hand"],
+ "moji": "✊"
+ },
+ "five": {
+ "moji": "5️⃣",
+ "unicode": "0035-20E3",
+ "unicode_alternates": ["0035-FE0F-20E3"],
+ "name": "digit five",
+ "shortname": ":five:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blue-square", "numbers", "prime"]
+ },
+ "flag_ac": {
+ "unicode": "1F1E6-1F1E8",
+ "unicode_alternates": [],
+ "name": "ascension",
+ "shortname": ":flag_ac:",
+ "category": "flags",
+ "aliases": [":ac:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "ac"]
+ },
+ "flag_ad": {
+ "unicode": "1F1E6-1F1E9",
+ "unicode_alternates": [],
+ "name": "andorra",
+ "shortname": ":flag_ad:",
+ "category": "flags",
+ "aliases": [":ad:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "ad"]
+ },
+ "flag_ae": {
+ "unicode": "1F1E6-1F1EA",
+ "unicode_alternates": [],
+ "name": "the united arab emirates",
+ "shortname": ":flag_ae:",
+ "category": "flags",
+ "aliases": [":ae:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "ae"]
+ },
+ "flag_af": {
+ "unicode": "1F1E6-1F1EB",
+ "unicode_alternates": [],
+ "name": "afghanistan",
+ "shortname": ":flag_af:",
+ "category": "flags",
+ "aliases": [":af:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "afghanestan", "af"]
+ },
+ "flag_ag": {
+ "unicode": "1F1E6-1F1EC",
+ "unicode_alternates": [],
+ "name": "antigua and barbuda",
+ "shortname": ":flag_ag:",
+ "category": "flags",
+ "aliases": [":ag:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "ag"]
+ },
+ "flag_ai": {
+ "unicode": "1F1E6-1F1EE",
+ "unicode_alternates": [],
+ "name": "anguilla",
+ "shortname": ":flag_ai:",
+ "category": "flags",
+ "aliases": [":ai:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "ai"]
+ },
+ "flag_al": {
+ "unicode": "1F1E6-1F1F1",
+ "unicode_alternates": [],
+ "name": "albania",
+ "shortname": ":flag_al:",
+ "category": "flags",
+ "aliases": [":al:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "shqiperia", "al"]
+ },
+ "flag_am": {
+ "unicode": "1F1E6-1F1F2",
+ "unicode_alternates": [],
+ "name": "armenia",
+ "shortname": ":flag_am:",
+ "category": "flags",
+ "aliases": [":am:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "hayastan", "am"]
+ },
+ "flag_ao": {
+ "unicode": "1F1E6-1F1F4",
+ "unicode_alternates": [],
+ "name": "angola",
+ "shortname": ":flag_ao:",
+ "category": "flags",
+ "aliases": [":ao:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "ao"]
+ },
+ "flag_ar": {
+ "unicode": "1F1E6-1F1F7",
+ "unicode_alternates": [],
+ "name": "argentina",
+ "shortname": ":flag_ar:",
+ "category": "flags",
+ "aliases": [":ar:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "ar"]
+ },
+ "flag_at": {
+ "unicode": "1F1E6-1F1F9",
+ "unicode_alternates": [],
+ "name": "austria",
+ "shortname": ":flag_at:",
+ "category": "flags",
+ "aliases": [":at:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "&ouml;sterreich", "osterreich", "at"]
+ },
+ "flag_au": {
+ "unicode": "1F1E6-1F1FA",
+ "unicode_alternates": [],
+ "name": "australia",
+ "shortname": ":flag_au:",
+ "category": "flags",
+ "aliases": [":au:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "au"]
+ },
+ "flag_aw": {
+ "unicode": "1F1E6-1F1FC",
+ "unicode_alternates": [],
+ "name": "aruba",
+ "shortname": ":flag_aw:",
+ "category": "flags",
+ "aliases": [":aw:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "aw"]
+ },
+ "flag_az": {
+ "unicode": "1F1E6-1F1FF",
+ "unicode_alternates": [],
+ "name": "azerbaijan",
+ "shortname": ":flag_az:",
+ "category": "flags",
+ "aliases": [":az:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "azarbaycan", "az"]
+ },
+ "flag_ba": {
+ "unicode": "1F1E7-1F1E6",
+ "unicode_alternates": [],
+ "name": "bosnia and herzegovina",
+ "shortname": ":flag_ba:",
+ "category": "flags",
+ "aliases": [":ba:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "bosna i hercegovina", "ba"]
+ },
+ "flag_bb": {
+ "unicode": "1F1E7-1F1E7",
+ "unicode_alternates": [],
+ "name": "barbados",
+ "shortname": ":flag_bb:",
+ "category": "flags",
+ "aliases": [":bb:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "bb"]
+ },
+ "flag_bd": {
+ "unicode": "1F1E7-1F1E9",
+ "unicode_alternates": [],
+ "name": "bangladesh",
+ "shortname": ":flag_bd:",
+ "category": "flags",
+ "aliases": [":bd:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "bd"]
+ },
+ "flag_be": {
+ "unicode": "1F1E7-1F1EA",
+ "unicode_alternates": [],
+ "name": "belgium",
+ "shortname": ":flag_be:",
+ "category": "flags",
+ "aliases": [":be:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "belgique", "belgie", "be"]
+ },
+ "flag_bf": {
+ "unicode": "1F1E7-1F1EB",
+ "unicode_alternates": [],
+ "name": "burkina faso",
+ "shortname": ":flag_bf:",
+ "category": "flags",
+ "aliases": [":bf:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "bf"]
+ },
+ "flag_bg": {
+ "unicode": "1F1E7-1F1EC",
+ "unicode_alternates": [],
+ "name": "bulgaria",
+ "shortname": ":flag_bg:",
+ "category": "flags",
+ "aliases": [":bg:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "bg"]
+ },
+ "flag_bh": {
+ "unicode": "1F1E7-1F1ED",
+ "unicode_alternates": [],
+ "name": "bahrain",
+ "shortname": ":flag_bh:",
+ "category": "flags",
+ "aliases": [":bh:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "al bahrayn", "bh"]
+ },
+ "flag_bi": {
+ "unicode": "1F1E7-1F1EE",
+ "unicode_alternates": [],
+ "name": "burundi",
+ "shortname": ":flag_bi:",
+ "category": "flags",
+ "aliases": [":bi:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "bi"]
+ },
+ "flag_bj": {
+ "unicode": "1F1E7-1F1EF",
+ "unicode_alternates": [],
+ "name": "benin",
+ "shortname": ":flag_bj:",
+ "category": "flags",
+ "aliases": [":bj:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "bj"]
+ },
+ "flag_black": {
+ "unicode": "1F3F4",
+ "unicode_alternates": [],
+ "name": "waving black flag",
+ "shortname": ":flag_black:",
+ "category": "objects_symbols",
+ "aliases": [":waving_black_flag:"],
+ "aliases_ascii": [],
+ "keywords": ["symbol", "signal"]
+ },
+ "flag_bm": {
+ "unicode": "1F1E7-1F1F2",
+ "unicode_alternates": [],
+ "name": "bermuda",
+ "shortname": ":flag_bm:",
+ "category": "flags",
+ "aliases": [":bm:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "bm"]
+ },
+ "flag_bn": {
+ "unicode": "1F1E7-1F1F3",
+ "unicode_alternates": [],
+ "name": "brunei",
+ "shortname": ":flag_bn:",
+ "category": "flags",
+ "aliases": [":bn:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "bn"]
+ },
+ "flag_bo": {
+ "unicode": "1F1E7-1F1F4",
+ "unicode_alternates": [],
+ "name": "bolivia",
+ "shortname": ":flag_bo:",
+ "category": "flags",
+ "aliases": [":bo:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "bo"]
+ },
+ "flag_br": {
+ "unicode": "1F1E7-1F1F7",
+ "unicode_alternates": [],
+ "name": "brazil",
+ "shortname": ":flag_br:",
+ "category": "flags",
+ "aliases": [":br:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "brasil", "br"]
+ },
+ "flag_bs": {
+ "unicode": "1F1E7-1F1F8",
+ "unicode_alternates": [],
+ "name": "the bahamas",
+ "shortname": ":flag_bs:",
+ "category": "flags",
+ "aliases": [":bs:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "bs"]
+ },
+ "flag_bt": {
+ "unicode": "1F1E7-1F1F9",
+ "unicode_alternates": [],
+ "name": "bhutan",
+ "shortname": ":flag_bt:",
+ "category": "flags",
+ "aliases": [":bt:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "bt"]
+ },
+ "flag_bw": {
+ "unicode": "1F1E7-1F1FC",
+ "unicode_alternates": [],
+ "name": "botswana",
+ "shortname": ":flag_bw:",
+ "category": "flags",
+ "aliases": [":bw:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "bw"]
+ },
+ "flag_by": {
+ "unicode": "1F1E7-1F1FE",
+ "unicode_alternates": [],
+ "name": "belarus",
+ "shortname": ":flag_by:",
+ "category": "flags",
+ "aliases": [":by:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "byelarus", "by"]
+ },
+ "flag_bz": {
+ "unicode": "1F1E7-1F1FF",
+ "unicode_alternates": [],
+ "name": "belize",
+ "shortname": ":flag_bz:",
+ "category": "flags",
+ "aliases": [":bz:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "bz"]
+ },
+ "flag_ca": {
+ "unicode": "1F1E8-1F1E6",
+ "unicode_alternates": [],
+ "name": "canada",
+ "shortname": ":flag_ca:",
+ "category": "flags",
+ "aliases": [":ca:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "ca"]
+ },
+ "flag_cd": {
+ "unicode": "1F1E8-1F1E9",
+ "unicode_alternates": [],
+ "name": "the democratic republic of the congo",
+ "shortname": ":flag_cd:",
+ "category": "flags",
+ "aliases": [":congo:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "r&eacute;publique d&eacute;mocratique du congo", "republique democratique du congo", "cd"]
+ },
+ "flag_cf": {
+ "unicode": "1F1E8-1F1EB",
+ "unicode_alternates": [],
+ "name": "central african republic",
+ "shortname": ":flag_cf:",
+ "category": "flags",
+ "aliases": [":cf:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "cf"]
+ },
+ "flag_cg": {
+ "unicode": "1F1E8-1F1EC",
+ "unicode_alternates": [],
+ "name": "the republic of the congo",
+ "shortname": ":flag_cg:",
+ "category": "flags",
+ "aliases": [":cg:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "cg"]
+ },
+ "flag_ch": {
+ "unicode": "1F1E8-1F1ED",
+ "unicode_alternates": [],
+ "name": "switzerland",
+ "shortname": ":flag_ch:",
+ "category": "flags",
+ "aliases": [":ch:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "swiss"]
+ },
+ "flag_ci": {
+ "unicode": "1F1E8-1F1EE",
+ "unicode_alternates": [],
+ "name": "cote d'ivoire",
+ "shortname": ":flag_ci:",
+ "category": "flags",
+ "aliases": [":ci:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "ci"]
+ },
+ "flag_cl": {
+ "unicode": "1F1E8-1F1F1",
+ "unicode_alternates": [],
+ "name": "chile",
+ "shortname": ":flag_cl:",
+ "category": "flags",
+ "aliases": [":chile:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "cl"]
+ },
+ "flag_cm": {
+ "unicode": "1F1E8-1F1F2",
+ "unicode_alternates": [],
+ "name": "cameroon",
+ "shortname": ":flag_cm:",
+ "category": "flags",
+ "aliases": [":cm:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "cm"]
+ },
+ "flag_cn": {
+ "unicode": "1F1E8-1F1F3",
+ "unicode_alternates": [],
+ "name": "china",
+ "shortname": ":flag_cn:",
+ "category": "flags",
+ "aliases": [":cn:"],
+ "aliases_ascii": [],
+ "keywords": ["chinese", "prc", "zhong guo", "country", "nation", "cn"]
+ },
+ "flag_co": {
+ "unicode": "1F1E8-1F1F4",
+ "unicode_alternates": [],
+ "name": "colombia",
+ "shortname": ":flag_co:",
+ "category": "flags",
+ "aliases": [":co:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "co"]
+ },
+ "flag_cr": {
+ "unicode": "1F1E8-1F1F7",
+ "unicode_alternates": [],
+ "name": "costa rica",
+ "shortname": ":flag_cr:",
+ "category": "flags",
+ "aliases": [":cr:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "cr"]
+ },
+ "flag_cu": {
+ "unicode": "1F1E8-1F1FA",
+ "unicode_alternates": [],
+ "name": "cuba",
+ "shortname": ":flag_cu:",
+ "category": "flags",
+ "aliases": [":cu:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "cu"]
+ },
+ "flag_cv": {
+ "unicode": "1F1E8-1F1FB",
+ "unicode_alternates": [],
+ "name": "cape verde",
+ "shortname": ":flag_cv:",
+ "category": "flags",
+ "aliases": [":cv:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "cabo verde", "cv"]
+ },
+ "flag_cy": {
+ "unicode": "1F1E8-1F1FE",
+ "unicode_alternates": [],
+ "name": "cyprus",
+ "shortname": ":flag_cy:",
+ "category": "flags",
+ "aliases": [":cy:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "kibris", "kypros", "cy"]
+ },
+ "flag_cz": {
+ "unicode": "1F1E8-1F1FF",
+ "unicode_alternates": [],
+ "name": "the czech republic",
+ "shortname": ":flag_cz:",
+ "category": "flags",
+ "aliases": [":cz:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "ceska republika", "cz"]
+ },
+ "flag_de": {
+ "unicode": "1F1E9-1F1EA",
+ "unicode_alternates": [],
+ "name": "germany",
+ "shortname": ":flag_de:",
+ "category": "flags",
+ "aliases": [":de:"],
+ "aliases_ascii": [],
+ "keywords": ["german", "nation", "deutschland", "country", "de"]
+ },
+ "flag_dj": {
+ "unicode": "1F1E9-1F1EF",
+ "unicode_alternates": [],
+ "name": "djibouti",
+ "shortname": ":flag_dj:",
+ "category": "flags",
+ "aliases": [":dj:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "dj"]
+ },
+ "flag_dk": {
+ "unicode": "1F1E9-1F1F0",
+ "unicode_alternates": [],
+ "name": "denmark",
+ "shortname": ":flag_dk:",
+ "category": "flags",
+ "aliases": [":dk:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "danmark", "dk"]
+ },
+ "flag_dm": {
+ "unicode": "1F1E9-1F1F2",
+ "unicode_alternates": [],
+ "name": "dominica",
+ "shortname": ":flag_dm:",
+ "category": "flags",
+ "aliases": [":dm:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "dm"]
+ },
+ "flag_do": {
+ "unicode": "1F1E9-1F1F4",
+ "unicode_alternates": [],
+ "name": "the dominican republic",
+ "shortname": ":flag_do:",
+ "category": "flags",
+ "aliases": [":do:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "do"]
+ },
+ "flag_dz": {
+ "unicode": "1F1E9-1F1FF",
+ "unicode_alternates": [],
+ "name": "algeria",
+ "shortname": ":flag_dz:",
+ "category": "flags",
+ "aliases": [":dz:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "al jaza'ir", "al jazair", "dz"]
+ },
+ "flag_ec": {
+ "unicode": "1F1EA-1F1E8",
+ "unicode_alternates": [],
+ "name": "ecuador",
+ "shortname": ":flag_ec:",
+ "category": "flags",
+ "aliases": [":ec:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "ec"]
+ },
+ "flag_ee": {
+ "unicode": "1F1EA-1F1EA",
+ "unicode_alternates": [],
+ "name": "estonia",
+ "shortname": ":flag_ee:",
+ "category": "flags",
+ "aliases": [":ee:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "eesti vabariik", "ee"]
+ },
+ "flag_eg": {
+ "unicode": "1F1EA-1F1EC",
+ "unicode_alternates": [],
+ "name": "egypt",
+ "shortname": ":flag_eg:",
+ "category": "flags",
+ "aliases": [":eg:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "misr", "eg"]
+ },
+ "flag_eh": {
+ "unicode": "1F1EA-1F1ED",
+ "unicode_alternates": [],
+ "name": "western sahara",
+ "shortname": ":flag_eh:",
+ "category": "flags",
+ "aliases": [":eh:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "aṣ-Ṣaḥrā’ al-gharbīyah", "sahra", "gharbiyah", "eh"]
+ },
+ "flag_er": {
+ "unicode": "1F1EA-1F1F7",
+ "unicode_alternates": [],
+ "name": "eritrea",
+ "shortname": ":flag_er:",
+ "category": "flags",
+ "aliases": [":er:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "hagere ertra", "er"]
+ },
+ "flag_es": {
+ "unicode": "1F1EA-1F1F8",
+ "unicode_alternates": [],
+ "name": "spain",
+ "shortname": ":flag_es:",
+ "category": "flags",
+ "aliases": [":es:"],
+ "aliases_ascii": [],
+ "keywords": ["nation", "espa&ntilde;a", "country", "espana", "es"]
+ },
+ "flag_et": {
+ "unicode": "1F1EA-1F1F9",
+ "unicode_alternates": [],
+ "name": "ethiopia",
+ "shortname": ":flag_et:",
+ "category": "flags",
+ "aliases": [":et:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "ityop'iya", "ityopiya", "et"]
+ },
+ "flag_fi": {
+ "unicode": "1F1EB-1F1EE",
+ "unicode_alternates": [],
+ "name": "finland",
+ "shortname": ":flag_fi:",
+ "category": "flags",
+ "aliases": [":fi:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "suomen tasavalta", "fi"]
+ },
+ "flag_fj": {
+ "unicode": "1F1EB-1F1EF",
+ "unicode_alternates": [],
+ "name": "fiji",
+ "shortname": ":flag_fj:",
+ "category": "flags",
+ "aliases": [":fj:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "fj"]
+ },
+ "flag_fk": {
+ "unicode": "1F1EB-1F1F0",
+ "unicode_alternates": [],
+ "name": "falkland islands",
+ "shortname": ":flag_fk:",
+ "category": "flags",
+ "aliases": [":fk:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "islas malvinas", "fk"]
+ },
+ "flag_fm": {
+ "unicode": "1F1EB-1F1F2",
+ "unicode_alternates": [],
+ "name": "micronesia",
+ "shortname": ":flag_fm:",
+ "category": "flags",
+ "aliases": [":fm:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "fm"]
+ },
+ "flag_fo": {
+ "unicode": "1F1EB-1F1F4",
+ "unicode_alternates": [],
+ "name": "faroe islands",
+ "shortname": ":flag_fo:",
+ "category": "flags",
+ "aliases": [":fo:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "foroyar", "fo"]
+ },
+ "flag_fr": {
+ "unicode": "1F1EB-1F1F7",
+ "unicode_alternates": [],
+ "name": "france",
+ "shortname": ":flag_fr:",
+ "category": "flags",
+ "aliases": [":fr:"],
+ "aliases_ascii": [],
+ "keywords": ["french", "nation", "country", "fr"]
+ },
+ "flag_ga": {
+ "unicode": "1F1EC-1F1E6",
+ "unicode_alternates": [],
+ "name": "gabon",
+ "shortname": ":flag_ga:",
+ "category": "flags",
+ "aliases": [":ga:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "ga"]
+ },
+ "flag_gb": {
+ "unicode": "1F1EC-1F1E7",
+ "unicode_alternates": [],
+ "name": "great britain",
+ "shortname": ":flag_gb:",
+ "category": "flags",
+ "aliases": [":gb:"],
+ "aliases_ascii": [],
+ "keywords": ["UK", "gb", "britsh", "nation", "united kingdom", "england", "country"]
+ },
+ "flag_gd": {
+ "unicode": "1F1EC-1F1E9",
+ "unicode_alternates": [],
+ "name": "grenada",
+ "shortname": ":flag_gd:",
+ "category": "flags",
+ "aliases": [":gd:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "gd"]
+ },
+ "flag_ge": {
+ "unicode": "1F1EC-1F1EA",
+ "unicode_alternates": [],
+ "name": "georgia",
+ "shortname": ":flag_ge:",
+ "category": "flags",
+ "aliases": [":ge:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "sak'art'velo", "sakartvelo", "ge"]
+ },
+ "flag_gh": {
+ "unicode": "1F1EC-1F1ED",
+ "unicode_alternates": [],
+ "name": "ghana",
+ "shortname": ":flag_gh:",
+ "category": "flags",
+ "aliases": [":gh:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "gh"]
+ },
+ "flag_gi": {
+ "unicode": "1F1EC-1F1EE",
+ "unicode_alternates": [],
+ "name": "gibraltar",
+ "shortname": ":flag_gi:",
+ "category": "flags",
+ "aliases": [":gi:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "gi"]
+ },
+ "flag_gl": {
+ "unicode": "1F1EC-1F1F1",
+ "unicode_alternates": [],
+ "name": "greenland",
+ "shortname": ":flag_gl:",
+ "category": "flags",
+ "aliases": [":gl:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "kalaallit nunaat", "gl"]
+ },
+ "flag_gm": {
+ "unicode": "1F1EC-1F1F2",
+ "unicode_alternates": [],
+ "name": "the gambia",
+ "shortname": ":flag_gm:",
+ "category": "flags",
+ "aliases": [":gm:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "gm"]
+ },
+ "flag_gn": {
+ "unicode": "1F1EC-1F1F3",
+ "unicode_alternates": [],
+ "name": "guinea",
+ "shortname": ":flag_gn:",
+ "category": "flags",
+ "aliases": [":gn:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "guinee", "gn"]
+ },
+ "flag_gq": {
+ "unicode": "1F1EC-1F1F6",
+ "unicode_alternates": [],
+ "name": "equatorial guinea",
+ "shortname": ":flag_gq:",
+ "category": "flags",
+ "aliases": [":gq:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "guinea ecuatorial", "gq"]
+ },
+ "flag_gr": {
+ "unicode": "1F1EC-1F1F7",
+ "unicode_alternates": [],
+ "name": "greece",
+ "shortname": ":flag_gr:",
+ "category": "flags",
+ "aliases": [":gr:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "ellas", "ellada", "gr"]
+ },
+ "flag_gt": {
+ "unicode": "1F1EC-1F1F9",
+ "unicode_alternates": [],
+ "name": "guatemala",
+ "shortname": ":flag_gt:",
+ "category": "flags",
+ "aliases": [":gt:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "gt"]
+ },
+ "flag_gu": {
+ "unicode": "1F1EC-1F1FA",
+ "unicode_alternates": [],
+ "name": "guam",
+ "shortname": ":flag_gu:",
+ "category": "flags",
+ "aliases": [":gu:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "gu"]
+ },
+ "flag_gw": {
+ "unicode": "1F1EC-1F1FC",
+ "unicode_alternates": [],
+ "name": "guinea-bissau",
+ "shortname": ":flag_gw:",
+ "category": "flags",
+ "aliases": [":gw:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "guine-bissau", "guine bissau", "gw"]
+ },
+ "flag_gy": {
+ "unicode": "1F1EC-1F1FE",
+ "unicode_alternates": [],
+ "name": "guyana",
+ "shortname": ":flag_gy:",
+ "category": "flags",
+ "aliases": [":gy:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "gy"]
+ },
+ "flag_hk": {
+ "unicode": "1F1ED-1F1F0",
+ "unicode_alternates": [],
+ "name": "hong kong",
+ "shortname": ":flag_hk:",
+ "category": "flags",
+ "aliases": [":hk:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "xianggang", "hk"]
+ },
+ "flag_hn": {
+ "unicode": "1F1ED-1F1F3",
+ "unicode_alternates": [],
+ "name": "honduras",
+ "shortname": ":flag_hn:",
+ "category": "flags",
+ "aliases": [":hn:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "hn"]
+ },
+ "flag_hr": {
+ "unicode": "1F1ED-1F1F7",
+ "unicode_alternates": [],
+ "name": "croatia",
+ "shortname": ":flag_hr:",
+ "category": "flags",
+ "aliases": [":hr:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "hrvatska", "hr"]
+ },
+ "flag_ht": {
+ "unicode": "1F1ED-1F1F9",
+ "unicode_alternates": [],
+ "name": "haiti",
+ "shortname": ":flag_ht:",
+ "category": "flags",
+ "aliases": [":ht:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "ht"]
+ },
+ "flag_hu": {
+ "unicode": "1F1ED-1F1FA",
+ "unicode_alternates": [],
+ "name": "hungary",
+ "shortname": ":flag_hu:",
+ "category": "flags",
+ "aliases": [":hu:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "magyarorszag", "hu"]
+ },
+ "flag_id": {
+ "unicode": "1F1EE-1F1E9",
+ "unicode_alternates": [],
+ "name": "indonesia",
+ "shortname": ":flag_id:",
+ "category": "flags",
+ "aliases": [":indonesia:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "id"]
+ },
+ "flag_ie": {
+ "unicode": "1F1EE-1F1EA",
+ "unicode_alternates": [],
+ "name": "ireland",
+ "shortname": ":flag_ie:",
+ "category": "flags",
+ "aliases": [":ie:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "&eacute;ire", "eire", "ie"]
+ },
+ "flag_il": {
+ "unicode": "1F1EE-1F1F1",
+ "unicode_alternates": [],
+ "name": "israel",
+ "shortname": ":flag_il:",
+ "category": "flags",
+ "aliases": [":il:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "yisra'el", "yisrael", "il"]
+ },
+ "flag_in": {
+ "unicode": "1F1EE-1F1F3",
+ "unicode_alternates": [],
+ "name": "india",
+ "shortname": ":flag_in:",
+ "category": "flags",
+ "aliases": [":in:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "bharat", "in"]
+ },
+ "flag_iq": {
+ "unicode": "1F1EE-1F1F6",
+ "unicode_alternates": [],
+ "name": "iraq",
+ "shortname": ":flag_iq:",
+ "category": "flags",
+ "aliases": [":iq:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "iq"]
+ },
+ "flag_ir": {
+ "unicode": "1F1EE-1F1F7",
+ "unicode_alternates": [],
+ "name": "iran",
+ "shortname": ":flag_ir:",
+ "category": "flags",
+ "aliases": [":ir:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "ir"]
+ },
+ "flag_is": {
+ "unicode": "1F1EE-1F1F8",
+ "unicode_alternates": [],
+ "name": "iceland",
+ "shortname": ":flag_is:",
+ "category": "flags",
+ "aliases": [":is:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "lyoveldio island", "is"]
+ },
+ "flag_it": {
+ "unicode": "1F1EE-1F1F9",
+ "unicode_alternates": [],
+ "name": "italy",
+ "shortname": ":flag_it:",
+ "category": "flags",
+ "aliases": [":it:"],
+ "aliases_ascii": [],
+ "keywords": ["italia", "country", "nation", "it"]
+ },
+ "flag_je": {
+ "unicode": "1F1EF-1F1EA",
+ "unicode_alternates": [],
+ "name": "jersey",
+ "shortname": ":flag_je:",
+ "category": "flags",
+ "aliases": [":je:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "je"]
+ },
+ "flag_jm": {
+ "unicode": "1F1EF-1F1F2",
+ "unicode_alternates": [],
+ "name": "jamaica",
+ "shortname": ":flag_jm:",
+ "category": "flags",
+ "aliases": [":jm:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "jm"]
+ },
+ "flag_jo": {
+ "unicode": "1F1EF-1F1F4",
+ "unicode_alternates": [],
+ "name": "jordan",
+ "shortname": ":flag_jo:",
+ "category": "flags",
+ "aliases": [":jo:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "al urdun", "jo"]
+ },
+ "flag_jp": {
+ "unicode": "1F1EF-1F1F5",
+ "unicode_alternates": [],
+ "name": "japan",
+ "shortname": ":flag_jp:",
+ "category": "flags",
+ "aliases": [":jp:"],
+ "aliases_ascii": [],
+ "keywords": ["nation", "nippon", "country", "jp"]
+ },
+ "flag_ke": {
+ "unicode": "1F1F0-1F1EA",
+ "unicode_alternates": [],
+ "name": "kenya",
+ "shortname": ":flag_ke:",
+ "category": "flags",
+ "aliases": [":ke:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "ke"]
+ },
+ "flag_kg": {
+ "unicode": "1F1F0-1F1EC",
+ "unicode_alternates": [],
+ "name": "kyrgyzstan",
+ "shortname": ":flag_kg:",
+ "category": "flags",
+ "aliases": [":kg:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "kyrgyz respublikasy", "kg"]
+ },
+ "flag_kh": {
+ "unicode": "1F1F0-1F1ED",
+ "unicode_alternates": [],
+ "name": "cambodia",
+ "shortname": ":flag_kh:",
+ "category": "flags",
+ "aliases": [":kh:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "kampuchea", "kh"]
+ },
+ "flag_ki": {
+ "unicode": "1F1F0-1F1EE",
+ "unicode_alternates": [],
+ "name": "kiribati",
+ "shortname": ":flag_ki:",
+ "category": "flags",
+ "aliases": [":ki:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "kiribati", "kiribas", "ki"]
+ },
+ "flag_km": {
+ "unicode": "1F1F0-1F1F2",
+ "unicode_alternates": [],
+ "name": "the comoros",
+ "shortname": ":flag_km:",
+ "category": "flags",
+ "aliases": [":km:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "km"]
+ },
+ "flag_kn": {
+ "unicode": "1F1F0-1F1F3",
+ "unicode_alternates": [],
+ "name": "saint kitts and nevis",
+ "shortname": ":flag_kn:",
+ "category": "flags",
+ "aliases": [":kn:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "kn"]
+ },
+ "flag_kp": {
+ "unicode": "1F1F0-1F1F5",
+ "unicode_alternates": [],
+ "name": "north korea",
+ "shortname": ":flag_kp:",
+ "category": "flags",
+ "aliases": [":kp:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "kp"]
+ },
+ "flag_kr": {
+ "unicode": "1F1F0-1F1F7",
+ "unicode_alternates": [],
+ "name": "korea",
+ "shortname": ":flag_kr:",
+ "category": "flags",
+ "aliases": [":kr:"],
+ "aliases_ascii": [],
+ "keywords": ["nation", "country", "south korea", "kr"]
+ },
+ "flag_kw": {
+ "unicode": "1F1F0-1F1FC",
+ "unicode_alternates": [],
+ "name": "kuwait",
+ "shortname": ":flag_kw:",
+ "category": "flags",
+ "aliases": [":kw:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "al kuwayt", "kw"]
+ },
+ "flag_ky": {
+ "unicode": "1F1F0-1F1FE",
+ "unicode_alternates": [],
+ "name": "cayman islands",
+ "shortname": ":flag_ky:",
+ "category": "flags",
+ "aliases": [":ky:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "ky"]
+ },
+ "flag_kz": {
+ "unicode": "1F1F0-1F1FF",
+ "unicode_alternates": [],
+ "name": "kazakhstan",
+ "shortname": ":flag_kz:",
+ "category": "flags",
+ "aliases": [":kz:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "qazaqstan", "kz"]
+ },
+ "flag_la": {
+ "unicode": "1F1F1-1F1E6",
+ "unicode_alternates": [],
+ "name": "laos",
+ "shortname": ":flag_la:",
+ "category": "flags",
+ "aliases": [":la:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "la"]
+ },
+ "flag_lb": {
+ "unicode": "1F1F1-1F1E7",
+ "unicode_alternates": [],
+ "name": "lebanon",
+ "shortname": ":flag_lb:",
+ "category": "flags",
+ "aliases": [":lb:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "lubnan", "lb"]
+ },
+ "flag_lc": {
+ "unicode": "1F1F1-1F1E8",
+ "unicode_alternates": [],
+ "name": "saint lucia",
+ "shortname": ":flag_lc:",
+ "category": "flags",
+ "aliases": [":lc:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "lc"]
+ },
+ "flag_li": {
+ "unicode": "1F1F1-1F1EE",
+ "unicode_alternates": [],
+ "name": "liechtenstein",
+ "shortname": ":flag_li:",
+ "category": "flags",
+ "aliases": [":li:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "li"]
+ },
+ "flag_lk": {
+ "unicode": "1F1F1-1F1F0",
+ "unicode_alternates": [],
+ "name": "sri lanka",
+ "shortname": ":flag_lk:",
+ "category": "flags",
+ "aliases": [":lk:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "lk"]
+ },
+ "flag_lr": {
+ "unicode": "1F1F1-1F1F7",
+ "unicode_alternates": [],
+ "name": "liberia",
+ "shortname": ":flag_lr:",
+ "category": "flags",
+ "aliases": [":lr:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "lr"]
+ },
+ "flag_ls": {
+ "unicode": "1F1F1-1F1F8",
+ "unicode_alternates": [],
+ "name": "lesotho",
+ "shortname": ":flag_ls:",
+ "category": "flags",
+ "aliases": [":ls:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "ls"]
+ },
+ "flag_lt": {
+ "unicode": "1F1F1-1F1F9",
+ "unicode_alternates": [],
+ "name": "lithuania",
+ "shortname": ":flag_lt:",
+ "category": "flags",
+ "aliases": [":lt:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "lietuva", "lt"]
+ },
+ "flag_lu": {
+ "unicode": "1F1F1-1F1FA",
+ "unicode_alternates": [],
+ "name": "luxembourg",
+ "shortname": ":flag_lu:",
+ "category": "flags",
+ "aliases": [":lu:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "luxembourg", "letzebuerg", "lu"]
+ },
+ "flag_lv": {
+ "unicode": "1F1F1-1F1FB",
+ "unicode_alternates": [],
+ "name": "latvia",
+ "shortname": ":flag_lv:",
+ "category": "flags",
+ "aliases": [":lv:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "latvija", "lv"]
+ },
+ "flag_ly": {
+ "unicode": "1F1F1-1F1FE",
+ "unicode_alternates": [],
+ "name": "libya",
+ "shortname": ":flag_ly:",
+ "category": "flags",
+ "aliases": [":ly:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "libiyah", "ly"]
+ },
+ "flag_ma": {
+ "unicode": "1F1F2-1F1E6",
+ "unicode_alternates": [],
+ "name": "morocco",
+ "shortname": ":flag_ma:",
+ "category": "flags",
+ "aliases": [":ma:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "al maghrib", "ma"]
+ },
+ "flag_mc": {
+ "unicode": "1F1F2-1F1E8",
+ "unicode_alternates": [],
+ "name": "monaco",
+ "shortname": ":flag_mc:",
+ "category": "flags",
+ "aliases": [":mc:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "mc"]
+ },
+ "flag_md": {
+ "unicode": "1F1F2-1F1E9",
+ "unicode_alternates": [],
+ "name": "moldova",
+ "shortname": ":flag_md:",
+ "category": "flags",
+ "aliases": [":md:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "md"]
+ },
+ "flag_me": {
+ "unicode": "1F1F2-1F1EA",
+ "unicode_alternates": [],
+ "name": "montenegro",
+ "shortname": ":flag_me:",
+ "category": "flags",
+ "aliases": [":me:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "crna gora", "me"]
+ },
+ "flag_mg": {
+ "unicode": "1F1F2-1F1EC",
+ "unicode_alternates": [],
+ "name": "madagascar",
+ "shortname": ":flag_mg:",
+ "category": "flags",
+ "aliases": [":mg:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "mg"]
+ },
+ "flag_mh": {
+ "unicode": "1F1F2-1F1ED",
+ "unicode_alternates": [],
+ "name": "the marshall islands",
+ "shortname": ":flag_mh:",
+ "category": "flags",
+ "aliases": [":mh:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "mh"]
+ },
+ "flag_mk": {
+ "unicode": "1F1F2-1F1F0",
+ "unicode_alternates": [],
+ "name": "macedonia",
+ "shortname": ":flag_mk:",
+ "category": "flags",
+ "aliases": [":mk:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "mk"]
+ },
+ "flag_ml": {
+ "unicode": "1F1F2-1F1F1",
+ "unicode_alternates": [],
+ "name": "mali",
+ "shortname": ":flag_ml:",
+ "category": "flags",
+ "aliases": [":ml:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "ml"]
+ },
+ "flag_mm": {
+ "unicode": "1F1F2-1F1F2",
+ "unicode_alternates": [],
+ "name": "myanmar",
+ "shortname": ":flag_mm:",
+ "category": "flags",
+ "aliases": [":mm:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "myanma naingngandaw", "mm"]
+ },
+ "flag_mn": {
+ "unicode": "1F1F2-1F1F3",
+ "unicode_alternates": [],
+ "name": "mongolia",
+ "shortname": ":flag_mn:",
+ "category": "flags",
+ "aliases": [":mn:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "mongol uls", "mn"]
+ },
+ "flag_mo": {
+ "unicode": "1F1F2-1F1F4",
+ "unicode_alternates": [],
+ "name": "macau",
+ "shortname": ":flag_mo:",
+ "category": "flags",
+ "aliases": [":mo:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "aomen", "mo"]
+ },
+ "flag_mr": {
+ "unicode": "1F1F2-1F1F7",
+ "unicode_alternates": [],
+ "name": "mauritania",
+ "shortname": ":flag_mr:",
+ "category": "flags",
+ "aliases": [":mr:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "muritaniyah", "mr"]
+ },
+ "flag_ms": {
+ "unicode": "1F1F2-1F1F8",
+ "unicode_alternates": [],
+ "name": "montserrat",
+ "shortname": ":flag_ms:",
+ "category": "flags",
+ "aliases": [":ms:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "ms"]
+ },
+ "flag_mt": {
+ "unicode": "1F1F2-1F1F9",
+ "unicode_alternates": [],
+ "name": "malta",
+ "shortname": ":flag_mt:",
+ "category": "flags",
+ "aliases": [":mt:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "mt"]
+ },
+ "flag_mu": {
+ "unicode": "1F1F2-1F1FA",
+ "unicode_alternates": [],
+ "name": "mauritius",
+ "shortname": ":flag_mu:",
+ "category": "flags",
+ "aliases": [":mu:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "mu"]
+ },
+ "flag_mv": {
+ "unicode": "1F1F2-1F1FB",
+ "unicode_alternates": [],
+ "name": "maldives",
+ "shortname": ":flag_mv:",
+ "category": "flags",
+ "aliases": [":mv:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "dhivehi raajje", "mv"]
+ },
+ "flag_mw": {
+ "unicode": "1F1F2-1F1FC",
+ "unicode_alternates": [],
+ "name": "malawi",
+ "shortname": ":flag_mw:",
+ "category": "flags",
+ "aliases": [":mw:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "mw"]
+ },
+ "flag_mx": {
+ "unicode": "1F1F2-1F1FD",
+ "unicode_alternates": [],
+ "name": "mexico",
+ "shortname": ":flag_mx:",
+ "category": "flags",
+ "aliases": [":mx:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "mx"]
+ },
+ "flag_my": {
+ "unicode": "1F1F2-1F1FE",
+ "unicode_alternates": [],
+ "name": "malaysia",
+ "shortname": ":flag_my:",
+ "category": "flags",
+ "aliases": [":my:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "my"]
+ },
+ "flag_mz": {
+ "unicode": "1F1F2-1F1FF",
+ "unicode_alternates": [],
+ "name": "mozambique",
+ "shortname": ":flag_mz:",
+ "category": "flags",
+ "aliases": [":mz:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "mocambique", "mz"]
+ },
+ "flag_na": {
+ "unicode": "1F1F3-1F1E6",
+ "unicode_alternates": [],
+ "name": "namibia",
+ "shortname": ":flag_na:",
+ "category": "flags",
+ "aliases": [":na:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "na"]
+ },
+ "flag_nc": {
+ "unicode": "1F1F3-1F1E8",
+ "unicode_alternates": [],
+ "name": "new caledonia",
+ "shortname": ":flag_nc:",
+ "category": "flags",
+ "aliases": [":nc:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "nouvelle", "cal&eacute;donie", "caledonie", "nc"]
+ },
+ "flag_ne": {
+ "unicode": "1F1F3-1F1EA",
+ "unicode_alternates": [],
+ "name": "niger",
+ "shortname": ":flag_ne:",
+ "category": "flags",
+ "aliases": [":ne:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "ne"]
+ },
+ "flag_ng": {
+ "unicode": "1F1F3-1F1EC",
+ "unicode_alternates": [],
+ "name": "nigeria",
+ "shortname": ":flag_ng:",
+ "category": "flags",
+ "aliases": [":nigeria:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "ng"]
+ },
+ "flag_ni": {
+ "unicode": "1F1F3-1F1EE",
+ "unicode_alternates": [],
+ "name": "nicaragua",
+ "shortname": ":flag_ni:",
+ "category": "flags",
+ "aliases": [":ni:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "ni"]
+ },
+ "flag_nl": {
+ "unicode": "1F1F3-1F1F1",
+ "unicode_alternates": [],
+ "name": "the netherlands",
+ "shortname": ":flag_nl:",
+ "category": "flags",
+ "aliases": [":nl:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "nederland", "holland", "nl"]
+ },
+ "flag_no": {
+ "unicode": "1F1F3-1F1F4",
+ "unicode_alternates": [],
+ "name": "norway",
+ "shortname": ":flag_no:",
+ "category": "flags",
+ "aliases": [":no:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "norge", "no"]
+ },
+ "flag_np": {
+ "unicode": "1F1F3-1F1F5",
+ "unicode_alternates": [],
+ "name": "nepal",
+ "shortname": ":flag_np:",
+ "category": "flags",
+ "aliases": [":np:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "np"]
+ },
+ "flag_nr": {
+ "unicode": "1F1F3-1F1F7",
+ "unicode_alternates": [],
+ "name": "nauru",
+ "shortname": ":flag_nr:",
+ "category": "flags",
+ "aliases": [":nr:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "nr"]
+ },
+ "flag_nu": {
+ "unicode": "1F1F3-1F1FA",
+ "unicode_alternates": [],
+ "name": "niue",
+ "shortname": ":flag_nu:",
+ "category": "flags",
+ "aliases": [":nu:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "nu"]
+ },
+ "flag_nz": {
+ "unicode": "1F1F3-1F1FF",
+ "unicode_alternates": [],
+ "name": "new zealand",
+ "shortname": ":flag_nz:",
+ "category": "flags",
+ "aliases": [":nz:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "aotearoa", "nz"]
+ },
+ "flag_om": {
+ "unicode": "1F1F4-1F1F2",
+ "unicode_alternates": [],
+ "name": "oman",
+ "shortname": ":flag_om:",
+ "category": "flags",
+ "aliases": [":om:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "saltanat uman", "om"]
+ },
+ "flag_pa": {
+ "unicode": "1F1F5-1F1E6",
+ "unicode_alternates": [],
+ "name": "panama",
+ "shortname": ":flag_pa:",
+ "category": "flags",
+ "aliases": [":pa:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "pa"]
+ },
+ "flag_pe": {
+ "unicode": "1F1F5-1F1EA",
+ "unicode_alternates": [],
+ "name": "peru",
+ "shortname": ":flag_pe:",
+ "category": "flags",
+ "aliases": [":pe:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "pe"]
+ },
+ "flag_pf": {
+ "unicode": "1F1F5-1F1EB",
+ "unicode_alternates": [],
+ "name": "french polynesia",
+ "shortname": ":flag_pf:",
+ "category": "flags",
+ "aliases": [":pf:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "polyn&eacute;sie fran&ccedil;aise", "polynesie francaise", "pf"]
+ },
+ "flag_pg": {
+ "unicode": "1F1F5-1F1EC",
+ "unicode_alternates": [],
+ "name": "papua new guinea",
+ "shortname": ":flag_pg:",
+ "category": "flags",
+ "aliases": [":pg:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "papua niu gini", "pg"]
+ },
+ "flag_ph": {
+ "unicode": "1F1F5-1F1ED",
+ "unicode_alternates": [],
+ "name": "the philippines",
+ "shortname": ":flag_ph:",
+ "category": "flags",
+ "aliases": [":ph:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "pilipinas", "ph"]
+ },
+ "flag_pk": {
+ "unicode": "1F1F5-1F1F0",
+ "unicode_alternates": [],
+ "name": "pakistan",
+ "shortname": ":flag_pk:",
+ "category": "flags",
+ "aliases": [":pk:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "pk"]
+ },
+ "flag_pl": {
+ "unicode": "1F1F5-1F1F1",
+ "unicode_alternates": [],
+ "name": "poland",
+ "shortname": ":flag_pl:",
+ "category": "flags",
+ "aliases": [":pl:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "polska", "pl"]
+ },
+ "flag_pr": {
+ "unicode": "1F1F5-1F1F7",
+ "unicode_alternates": [],
+ "name": "puerto rico",
+ "shortname": ":flag_pr:",
+ "category": "flags",
+ "aliases": [":pr:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "pr"]
+ },
+ "flag_ps": {
+ "unicode": "1F1F5-1F1F8",
+ "unicode_alternates": [],
+ "name": "palestinian authority",
+ "shortname": ":flag_ps:",
+ "category": "flags",
+ "aliases": [":ps:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "ps"]
+ },
+ "flag_pt": {
+ "unicode": "1F1F5-1F1F9",
+ "unicode_alternates": [],
+ "name": "portugal",
+ "shortname": ":flag_pt:",
+ "category": "flags",
+ "aliases": [":pt:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "pt"]
+ },
+ "flag_pw": {
+ "unicode": "1F1F5-1F1FC",
+ "unicode_alternates": [],
+ "name": "palau",
+ "shortname": ":flag_pw:",
+ "category": "flags",
+ "aliases": [":pw:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "belau", "pw"]
+ },
+ "flag_py": {
+ "unicode": "1F1F5-1F1FE",
+ "unicode_alternates": [],
+ "name": "paraguay",
+ "shortname": ":flag_py:",
+ "category": "flags",
+ "aliases": [":py:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "py"]
+ },
+ "flag_qa": {
+ "unicode": "1F1F6-1F1E6",
+ "unicode_alternates": [],
+ "name": "qatar",
+ "shortname": ":flag_qa:",
+ "category": "flags",
+ "aliases": [":qa:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "dawlat qatar", "qa"]
+ },
+ "flag_ro": {
+ "unicode": "1F1F7-1F1F4",
+ "unicode_alternates": [],
+ "name": "romania",
+ "shortname": ":flag_ro:",
+ "category": "flags",
+ "aliases": [":ro:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "ro"]
+ },
+ "flag_rs": {
+ "unicode": "1F1F7-1F1F8",
+ "unicode_alternates": [],
+ "name": "serbia",
+ "shortname": ":flag_rs:",
+ "category": "flags",
+ "aliases": [":rs:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "srbija", "rs"]
+ },
+ "flag_ru": {
+ "unicode": "1F1F7-1F1FA",
+ "unicode_alternates": [],
+ "name": "russia",
+ "shortname": ":flag_ru:",
+ "category": "flags",
+ "aliases": [":ru:"],
+ "aliases_ascii": [],
+ "keywords": ["nation", "russian", "country", "ru"]
+ },
+ "flag_rw": {
+ "unicode": "1F1F7-1F1FC",
+ "unicode_alternates": [],
+ "name": "rwanda",
+ "shortname": ":flag_rw:",
+ "category": "flags",
+ "aliases": [":rw:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "rw"]
+ },
+ "flag_sa": {
+ "unicode": "1F1F8-1F1E6",
+ "unicode_alternates": [],
+ "name": "saudi arabia",
+ "shortname": ":flag_sa:",
+ "category": "flags",
+ "aliases": [":saudiarabia:", ":saudi:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "al arabiyah as suudiyah", "sa"]
+ },
+ "flag_sb": {
+ "unicode": "1F1F8-1F1E7",
+ "unicode_alternates": [],
+ "name": "the solomon islands",
+ "shortname": ":flag_sb:",
+ "category": "flags",
+ "aliases": [":sb:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "sb"]
+ },
+ "flag_sc": {
+ "unicode": "1F1F8-1F1E8",
+ "unicode_alternates": [],
+ "name": "the seychelles",
+ "shortname": ":flag_sc:",
+ "category": "flags",
+ "aliases": [":sc:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "seychelles", "sc"]
+ },
+ "flag_sd": {
+ "unicode": "1F1F8-1F1E9",
+ "unicode_alternates": [],
+ "name": "sudan",
+ "shortname": ":flag_sd:",
+ "category": "flags",
+ "aliases": [":sd:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "as-sudan", "sd"]
+ },
+ "flag_se": {
+ "unicode": "1F1F8-1F1EA",
+ "unicode_alternates": [],
+ "name": "sweden",
+ "shortname": ":flag_se:",
+ "category": "flags",
+ "aliases": [":se:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "sverige", "se"]
+ },
+ "flag_sg": {
+ "unicode": "1F1F8-1F1EC",
+ "unicode_alternates": [],
+ "name": "singapore",
+ "shortname": ":flag_sg:",
+ "category": "flags",
+ "aliases": [":sg:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "sg"]
+ },
+ "flag_sh": {
+ "unicode": "1F1F8-1F1ED",
+ "unicode_alternates": [],
+ "name": "saint helena",
+ "shortname": ":flag_sh:",
+ "category": "flags",
+ "aliases": [":sh:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "sh"]
+ },
+ "flag_si": {
+ "unicode": "1F1F8-1F1EE",
+ "unicode_alternates": [],
+ "name": "slovenia",
+ "shortname": ":flag_si:",
+ "category": "flags",
+ "aliases": [":si:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "slovenija", "si"]
+ },
+ "flag_sk": {
+ "unicode": "1F1F8-1F1F0",
+ "unicode_alternates": [],
+ "name": "slovakia",
+ "shortname": ":flag_sk:",
+ "category": "flags",
+ "aliases": [":sk:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "sk"]
+ },
+ "flag_sl": {
+ "unicode": "1F1F8-1F1F1",
+ "unicode_alternates": [],
+ "name": "sierra leone",
+ "shortname": ":flag_sl:",
+ "category": "flags",
+ "aliases": [":sl:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "sl"]
+ },
+ "flag_sm": {
+ "unicode": "1F1F8-1F1F2",
+ "unicode_alternates": [],
+ "name": "san marino",
+ "shortname": ":flag_sm:",
+ "category": "flags",
+ "aliases": [":sm:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "sm"]
+ },
+ "flag_sn": {
+ "unicode": "1F1F8-1F1F3",
+ "unicode_alternates": [],
+ "name": "senegal",
+ "shortname": ":flag_sn:",
+ "category": "flags",
+ "aliases": [":sn:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "sn"]
+ },
+ "flag_so": {
+ "unicode": "1F1F8-1F1F4",
+ "unicode_alternates": [],
+ "name": "somalia",
+ "shortname": ":flag_so:",
+ "category": "flags",
+ "aliases": [":so:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "so"]
+ },
+ "flag_sr": {
+ "unicode": "1F1F8-1F1F7",
+ "unicode_alternates": [],
+ "name": "suriname",
+ "shortname": ":flag_sr:",
+ "category": "flags",
+ "aliases": [":sr:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "sr"]
+ },
+ "flag_st": {
+ "unicode": "1F1F8-1F1F9",
+ "unicode_alternates": [],
+ "name": "sao tome and principe",
+ "shortname": ":flag_st:",
+ "category": "flags",
+ "aliases": [":st:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "sao tome e principe", "st"]
+ },
+ "flag_sv": {
+ "unicode": "1F1F8-1F1FB",
+ "unicode_alternates": [],
+ "name": "el salvador",
+ "shortname": ":flag_sv:",
+ "category": "flags",
+ "aliases": [":sv:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "sv"]
+ },
+ "flag_sy": {
+ "unicode": "1F1F8-1F1FE",
+ "unicode_alternates": [],
+ "name": "syria",
+ "shortname": ":flag_sy:",
+ "category": "flags",
+ "aliases": [":sy:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "sy"]
+ },
+ "flag_sz": {
+ "unicode": "1F1F8-1F1FF",
+ "unicode_alternates": [],
+ "name": "swaziland",
+ "shortname": ":flag_sz:",
+ "category": "flags",
+ "aliases": [":sz:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "sz"]
+ },
+ "flag_td": {
+ "unicode": "1F1F9-1F1E9",
+ "unicode_alternates": [],
+ "name": "chad",
+ "shortname": ":flag_td:",
+ "category": "flags",
+ "aliases": [":td:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "tchad", "td"]
+ },
+ "flag_tg": {
+ "unicode": "1F1F9-1F1EC",
+ "unicode_alternates": [],
+ "name": "togo",
+ "shortname": ":flag_tg:",
+ "category": "flags",
+ "aliases": [":tg:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "republique togolaise", "tg"]
+ },
+ "flag_th": {
+ "unicode": "1F1F9-1F1ED",
+ "unicode_alternates": [],
+ "name": "thailand",
+ "shortname": ":flag_th:",
+ "category": "flags",
+ "aliases": [":th:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "prathet thai", "th"]
+ },
+ "flag_tj": {
+ "unicode": "1F1F9-1F1EF",
+ "unicode_alternates": [],
+ "name": "tajikistan",
+ "shortname": ":flag_tj:",
+ "category": "flags",
+ "aliases": [":tj:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "jumhurii tojikiston", "tj"]
+ },
+ "flag_tl": {
+ "unicode": "1F1F9-1F1F1",
+ "unicode_alternates": [],
+ "name": "east timor",
+ "shortname": ":flag_tl:",
+ "category": "flags",
+ "aliases": [":tl:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "tl"]
+ },
+ "flag_tm": {
+ "unicode": "1F1F9-1F1F2",
+ "unicode_alternates": [],
+ "name": "turkmenistan",
+ "shortname": ":flag_tm:",
+ "category": "flags",
+ "aliases": [":turkmenistan:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "tm"]
+ },
+ "flag_tn": {
+ "unicode": "1F1F9-1F1F3",
+ "unicode_alternates": [],
+ "name": "tunisia",
+ "shortname": ":flag_tn:",
+ "category": "flags",
+ "aliases": [":tn:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "tunis", "tn"]
+ },
+ "flag_to": {
+ "unicode": "1F1F9-1F1F4",
+ "unicode_alternates": [],
+ "name": "tonga",
+ "shortname": ":flag_to:",
+ "category": "flags",
+ "aliases": [":to:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "to"]
+ },
+ "flag_tr": {
+ "unicode": "1F1F9-1F1F7",
+ "unicode_alternates": [],
+ "name": "turkey",
+ "shortname": ":flag_tr:",
+ "category": "flags",
+ "aliases": [":tr:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "turkiye"]
+ },
+ "flag_tt": {
+ "unicode": "1F1F9-1F1F9",
+ "unicode_alternates": [],
+ "name": "trinidad and tobago",
+ "shortname": ":flag_tt:",
+ "category": "flags",
+ "aliases": [":tt:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "tt"]
+ },
+ "flag_tv": {
+ "unicode": "1F1F9-1F1FB",
+ "unicode_alternates": [],
+ "name": "tuvalu",
+ "shortname": ":flag_tv:",
+ "category": "flags",
+ "aliases": [":tuvalu:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "tv"]
+ },
+ "flag_tw": {
+ "unicode": "1F1F9-1F1FC",
+ "unicode_alternates": [],
+ "name": "the republic of china",
+ "shortname": ":flag_tw:",
+ "category": "flags",
+ "aliases": [":tw:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "taiwan", "tw"]
+ },
+ "flag_tz": {
+ "unicode": "1F1F9-1F1FF",
+ "unicode_alternates": [],
+ "name": "tanzania",
+ "shortname": ":flag_tz:",
+ "category": "flags",
+ "aliases": [":tz:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "tz"]
+ },
+ "flag_ua": {
+ "unicode": "1F1FA-1F1E6",
+ "unicode_alternates": [],
+ "name": "ukraine",
+ "shortname": ":flag_ua:",
+ "category": "flags",
+ "aliases": [":ua:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "ukrayina", "ua"]
+ },
+ "flag_ug": {
+ "unicode": "1F1FA-1F1EC",
+ "unicode_alternates": [],
+ "name": "uganda",
+ "shortname": ":flag_ug:",
+ "category": "flags",
+ "aliases": [":ug:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "ug"]
+ },
+ "flag_us": {
+ "unicode": "1F1FA-1F1F8",
+ "unicode_alternates": [],
+ "name": "united states",
+ "shortname": ":flag_us:",
+ "category": "flags",
+ "aliases": [":us:"],
+ "aliases_ascii": [],
+ "keywords": ["american", "country", "nation", "usa", "united states of america", "america", "old glory", "us"]
+ },
+ "flag_uy": {
+ "unicode": "1F1FA-1F1FE",
+ "unicode_alternates": [],
+ "name": "uruguay",
+ "shortname": ":flag_uy:",
+ "category": "flags",
+ "aliases": [":uy:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "uy"]
+ },
+ "flag_uz": {
+ "unicode": "1F1FA-1F1FF",
+ "unicode_alternates": [],
+ "name": "uzbekistan",
+ "shortname": ":flag_uz:",
+ "category": "flags",
+ "aliases": [":uz:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "uzbekiston respublikasi", "uz"]
+ },
+ "flag_va": {
+ "unicode": "1F1FB-1F1E6",
+ "unicode_alternates": [],
+ "name": "the vatican city",
+ "shortname": ":flag_va:",
+ "category": "flags",
+ "aliases": [":va:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "va"]
+ },
+ "flag_vc": {
+ "unicode": "1F1FB-1F1E8",
+ "unicode_alternates": [],
+ "name": "saint vincent and the grenadines",
+ "shortname": ":flag_vc:",
+ "category": "flags",
+ "aliases": [":vc:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "vc"]
+ },
+ "flag_ve": {
+ "unicode": "1F1FB-1F1EA",
+ "unicode_alternates": [],
+ "name": "venezuela",
+ "shortname": ":flag_ve:",
+ "category": "flags",
+ "aliases": [":ve:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "ve"]
+ },
+ "flag_vi": {
+ "unicode": "1F1FB-1F1EE",
+ "unicode_alternates": [],
+ "name": "u.s. virgin islands",
+ "shortname": ":flag_vi:",
+ "category": "flags",
+ "aliases": [":vi:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "vi"]
+ },
+ "flag_vn": {
+ "unicode": "1F1FB-1F1F3",
+ "unicode_alternates": [],
+ "name": "vietnam",
+ "shortname": ":flag_vn:",
+ "category": "flags",
+ "aliases": [":vn:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "viet nam", "vn"]
+ },
+ "flag_vu": {
+ "unicode": "1F1FB-1F1FA",
+ "unicode_alternates": [],
+ "name": "vanuatu",
+ "shortname": ":flag_vu:",
+ "category": "flags",
+ "aliases": [":vu:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "vu"]
+ },
+ "flag_wf": {
+ "unicode": "1F1FC-1F1EB",
+ "unicode_alternates": [],
+ "name": "wallis and futuna",
+ "shortname": ":flag_wf:",
+ "category": "flags",
+ "aliases": [":wf:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "wf"]
+ },
+ "flag_white": {
+ "unicode": "1F3F3",
+ "unicode_alternates": [],
+ "name": "waving white flag",
+ "shortname": ":flag_white:",
+ "category": "objects_symbols",
+ "aliases": [":waving_white_flag:"],
+ "aliases_ascii": [],
+ "keywords": ["symbol", "signal"]
+ },
+ "flag_ws": {
+ "unicode": "1F1FC-1F1F8",
+ "unicode_alternates": [],
+ "name": "samoa",
+ "shortname": ":flag_ws:",
+ "category": "flags",
+ "aliases": [":ws:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "american samoa", "ws"]
+ },
+ "flag_xk": {
+ "unicode": "1F1FD-1F1F0",
+ "unicode_alternates": [],
+ "name": "kosovo",
+ "shortname": ":flag_xk:",
+ "category": "flags",
+ "aliases": [":xk:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "xk"]
+ },
+ "flag_ye": {
+ "unicode": "1F1FE-1F1EA",
+ "unicode_alternates": [],
+ "name": "yemen",
+ "shortname": ":flag_ye:",
+ "category": "flags",
+ "aliases": [":ye:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "al yaman", "ye"]
+ },
+ "flag_za": {
+ "unicode": "1F1FF-1F1E6",
+ "unicode_alternates": [],
+ "name": "south africa",
+ "shortname": ":flag_za:",
+ "category": "flags",
+ "aliases": [":za:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation"]
+ },
+ "flag_zm": {
+ "unicode": "1F1FF-1F1F2",
+ "unicode_alternates": [],
+ "name": "zambia",
+ "shortname": ":flag_zm:",
+ "category": "flags",
+ "aliases": [":zm:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "zm"]
+ },
+ "flag_zw": {
+ "unicode": "1F1FF-1F1FC",
+ "unicode_alternates": [],
+ "name": "zimbabwe",
+ "shortname": ":flag_zw:",
+ "category": "flags",
+ "aliases": [":zw:"],
+ "aliases_ascii": [],
+ "keywords": ["country", "nation", "zw"]
+ },
+ "flags": {
+ "unicode": "1F38F",
+ "unicode_alternates": [],
+ "name": "carp streamer",
+ "shortname": ":flags:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["banner", "carp", "fish", "japanese", "koinobori", "children", "kids", "boys", "celebration", "happiness", "carp", "streamers", "japanese", "holiday", "flags"],
+ "moji": "🎏"
+ },
+ "flashlight": {
+ "unicode": "1F526",
+ "unicode_alternates": [],
+ "name": "electric torch",
+ "shortname": ":flashlight:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["dark"],
+ "moji": "🔦"
+ },
+ "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:"],
+ "aliases_ascii": [],
+ "keywords": ["oldschool", "save", "technology", "storage", "information", "computer", "drive", "megabyte"]
+ },
+ "floppy_disk": {
+ "unicode": "1F4BE",
+ "unicode_alternates": [],
+ "name": "floppy disk",
+ "shortname": ":floppy_disk:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["oldschool", "save", "technology", "floppy", "disk", "storage", "information", "computer", "drive", "megabyte"],
+ "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",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["playing", "card", "flower", "game", "august", "moon", "special"],
+ "moji": "🎴"
+ },
+ "flushed": {
+ "unicode": "1F633",
+ "unicode_alternates": [],
+ "name": "flushed face",
+ "shortname": ":flushed:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [":$", "=$"],
+ "keywords": ["blush", "face", "flattered", "flush", "blush", "red", "pink", "cheeks", "shy"],
+ "moji": "😳"
+ },
+ "fog": {
+ "unicode": "1F32B",
+ "unicode_alternates": [],
+ "name": "fog",
+ "shortname": ":fog:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["weather", "damp", "cloud", "hazy"]
+ },
+ "foggy": {
+ "unicode": "1F301",
+ "unicode_alternates": [],
+ "name": "foggy",
+ "shortname": ":foggy:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["mountain", "photo", "bridge", "weather", "fog", "foggy"],
+ "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",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["NFL", "balls", "sports", "football", "ball", "sport", "america", "american"],
+ "moji": "🏈"
+ },
+ "footprints": {
+ "unicode": "1F463",
+ "unicode_alternates": [],
+ "name": "footprints",
+ "shortname": ":footprints:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["feet"],
+ "moji": "👣"
+ },
+ "fork_and_knife": {
+ "unicode": "1F374",
+ "unicode_alternates": [],
+ "name": "fork and knife",
+ "shortname": ":fork_and_knife:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["cutlery", "kitchen", "fork", "knife", "restaurant", "meal", "food", "eat"],
+ "moji": "🍴"
+ },
+ "fork_knife_plate": {
+ "unicode": "1F37D",
+ "unicode_alternates": [],
+ "name": "fork and knife with plate",
+ "shortname": ":fork_knife_plate:",
+ "category": "travel_places",
+ "aliases": [":fork_and_knife_with_plate:"],
+ "aliases_ascii": [],
+ "keywords": ["meal", "food", "breakfast", "lunch", "dinner", "utensils", "setting"]
+ },
+ "fountain": {
+ "unicode": "26F2",
+ "unicode_alternates": ["26F2-FE0F"],
+ "name": "fountain",
+ "shortname": ":fountain:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["photo"],
+ "moji": "⛲"
+ },
+ "four": {
+ "moji": "4️⃣",
+ "unicode": "0034-20E3",
+ "unicode_alternates": ["0034-FE0F-20E3"],
+ "name": "digit four",
+ "shortname": ":four:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["4", "blue-square", "numbers"]
+ },
+ "four_leaf_clover": {
+ "unicode": "1F340",
+ "unicode_alternates": [],
+ "name": "four leaf clover",
+ "shortname": ":four_leaf_clover:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["lucky", "nature", "plant", "vegetable", "clover", "four", "leaf", "luck", "irish", "saint", "patrick", "green"],
+ "moji": "🍀"
+ },
+ "frame_photo": {
+ "unicode": "1F5BC",
+ "unicode_alternates": [],
+ "name": "frame with picture",
+ "shortname": ":frame_photo:",
+ "category": "objects_symbols",
+ "aliases": [":frame_with_picture:"],
+ "aliases_ascii": [],
+ "keywords": ["photo"]
+ },
+ "frame_tiles": {
+ "unicode": "1F5BD",
+ "unicode_alternates": [],
+ "name": "frame with tiles",
+ "shortname": ":frame_tiles:",
+ "category": "objects_symbols",
+ "aliases": [":frame_with_tiles:"],
+ "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:"],
+ "aliases_ascii": [],
+ "keywords": ["photo", "painting"]
+ },
+ "free": {
+ "unicode": "1F193",
+ "unicode_alternates": [],
+ "name": "squared free",
+ "shortname": ":free:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blue-square", "words"],
+ "moji": "🆓"
+ },
+ "fried_shrimp": {
+ "unicode": "1F364",
+ "unicode_alternates": [],
+ "name": "fried shrimp",
+ "shortname": ":fried_shrimp:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "food", "shrimp", "fried", "seafood", "small", "fish"],
+ "moji": "🍤"
+ },
+ "fries": {
+ "unicode": "1F35F",
+ "unicode_alternates": [],
+ "name": "french fries",
+ "shortname": ":fries:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["chips", "food", "fries", "french", "potato", "fry", "russet", "idaho"],
+ "moji": "🍟"
+ },
+ "frog": {
+ "unicode": "1F438",
+ "unicode_alternates": [],
+ "name": "frog face",
+ "shortname": ":frog:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "nature"],
+ "moji": "🐸"
+ },
+ "frowning": {
+ "unicode": "1F626",
+ "unicode_alternates": [],
+ "name": "frowning face with open mouth",
+ "shortname": ":frowning:",
+ "category": "emoticons",
+ "aliases": [":anguished:"],
+ "aliases_ascii": [],
+ "keywords": ["aw", "face", "frown", "sad", "pout", "sulk", "glower"],
+ "moji": "😦"
+ },
+ "fuelpump": {
+ "unicode": "26FD",
+ "unicode_alternates": ["26FD-FE0F"],
+ "name": "fuel pump",
+ "shortname": ":fuelpump:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["gas station", "petroleum"],
+ "moji": "⛽"
+ },
+ "full_moon": {
+ "unicode": "1F315",
+ "unicode_alternates": [],
+ "name": "full moon symbol",
+ "shortname": ":full_moon:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["nature", "yellow", "moon", "full", "sky", "night", "cheese", "phase", "monster", "spooky", "werewolves", "twilight"],
+ "moji": "🌕"
+ },
+ "full_moon_with_face": {
+ "unicode": "1F31D",
+ "unicode_alternates": [],
+ "name": "full moon with face",
+ "shortname": ":full_moon_with_face:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["night", "moon", "full", "anthropomorphic", "face", "sky", "night", "cheese", "phase", "spooky", "werewolves", "monsters"],
+ "moji": "🌝"
+ },
+ "game_die": {
+ "unicode": "1F3B2",
+ "unicode_alternates": [],
+ "name": "game die",
+ "shortname": ":game_die:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["dice", "game", "die", "dice", "craps", "gamble", "play"],
+ "moji": "🎲"
+ },
+ "gem": {
+ "unicode": "1F48E",
+ "unicode_alternates": [],
+ "name": "gem stone",
+ "shortname": ":gem:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blue", "ruby"],
+ "moji": "💎"
+ },
+ "gemini": {
+ "unicode": "264A",
+ "unicode_alternates": ["264A-FE0F"],
+ "name": "gemini",
+ "shortname": ":gemini:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["gemini", "twins", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "sign", "zodiac", "horoscope"],
+ "moji": "♊"
+ },
+ "ghost": {
+ "unicode": "1F47B",
+ "unicode_alternates": [],
+ "name": "ghost",
+ "shortname": ":ghost:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["halloween"],
+ "moji": "👻"
+ },
+ "gift": {
+ "unicode": "1F381",
+ "unicode_alternates": [],
+ "name": "wrapped present",
+ "shortname": ":gift:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["birthday", "christmas", "present", "xmas", "gift", "present", "wrap", "package", "birthday", "wedding"],
+ "moji": "🎁"
+ },
+ "gift_heart": {
+ "unicode": "1F49D",
+ "unicode_alternates": [],
+ "name": "heart with ribbon",
+ "shortname": ":gift_heart:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["love", "valentines"],
+ "moji": "💝"
+ },
+ "girl": {
+ "unicode": "1F467",
+ "unicode_alternates": [],
+ "name": "girl",
+ "shortname": ":girl:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["female", "woman"],
+ "moji": "👧"
+ },
+ "girls_symbol": {
+ "unicode": "1F6CA",
+ "unicode_alternates": [],
+ "name": "girls symbol",
+ "shortname": ":girls_symbol:",
+ "category": "objects_symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["female", "child"]
+ },
+ "globe_with_meridians": {
+ "unicode": "1F310",
+ "unicode_alternates": [],
+ "name": "globe with meridians",
+ "shortname": ":globe_with_meridians:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["earth", "international", "world", "earth", "meridian", "globe", "space", "planet", "home"],
+ "moji": "🌐"
+ },
+ "goat": {
+ "unicode": "1F410",
+ "unicode_alternates": [],
+ "name": "goat",
+ "shortname": ":goat:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "nature", "goat", "sheep", "kid", "billy", "livestock"],
+ "moji": "🐐"
+ },
+ "golf": {
+ "unicode": "26F3",
+ "unicode_alternates": ["26F3-FE0F"],
+ "name": "flag in hole",
+ "shortname": ":golf:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["business", "sports"],
+ "moji": "⛳"
+ },
+ "golfer": {
+ "unicode": "1F3CC",
+ "unicode_alternates": [],
+ "name": "golfer",
+ "shortname": ":golfer:",
+ "category": "activity",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["sport", "par", "birdie", "eagle", "mulligan"]
+ },
+ "grapes": {
+ "unicode": "1F347",
+ "unicode_alternates": [],
+ "name": "grapes",
+ "shortname": ":grapes:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["food", "fruit", "grapes", "wine", "vinegar", "fruit", "cluster", "vine"],
+ "moji": "🍇"
+ },
+ "green_apple": {
+ "unicode": "1F34F",
+ "unicode_alternates": [],
+ "name": "green apple",
+ "shortname": ":green_apple:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["fruit", "nature", "apple", "fruit", "green", "pie", "granny", "smith", "core"],
+ "moji": "🍏"
+ },
+ "green_book": {
+ "unicode": "1F4D7",
+ "unicode_alternates": [],
+ "name": "green book",
+ "shortname": ":green_book:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["knowledge", "library", "read"],
+ "moji": "📗"
+ },
+ "green_heart": {
+ "unicode": "1F49A",
+ "unicode_alternates": [],
+ "name": "green heart",
+ "shortname": ":green_heart:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["affection", "like", "love", "valentines", "green", "heart", "love", "nature", "rebirth", "reborn", "jealous", "clingy", "envious", "possessive"],
+ "moji": "💚"
+ },
+ "grey_exclamation": {
+ "unicode": "2755",
+ "unicode_alternates": [],
+ "name": "white exclamation mark ornament",
+ "shortname": ":grey_exclamation:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["surprise"],
+ "moji": "❕"
+ },
+ "grey_question": {
+ "unicode": "2754",
+ "unicode_alternates": [],
+ "name": "white question mark ornament",
+ "shortname": ":grey_question:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["doubts"],
+ "moji": "❔"
+ },
+ "grimacing": {
+ "unicode": "1F62C",
+ "unicode_alternates": [],
+ "name": "grimacing face",
+ "shortname": ":grimacing:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["face", "grimace", "teeth", "grimace", "disapprove", "pain"],
+ "moji": "😬"
+ },
+ "grin": {
+ "unicode": "1F601",
+ "unicode_alternates": [],
+ "name": "grinning face with smiling eyes",
+ "shortname": ":grin:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["face", "happy", "joy", "smile", "grin", "grinning", "smiling", "smile", "smiley"],
+ "moji": "😁"
+ },
+ "grinning": {
+ "unicode": "1F600",
+ "unicode_alternates": [],
+ "name": "grinning face",
+ "shortname": ":grinning:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["face", "happy", "joy", "smile", "grin", "grinning", "smiling", "smile", "smiley"],
+ "moji": "🕧"
+ },
+ "guardsman": {
+ "unicode": "1F482",
+ "unicode_alternates": [],
+ "name": "guardsman",
+ "shortname": ":guardsman:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["british", "gb", "male", "man", "uk", "guardsman", "guard", "bearskin", "hat", "british", "queen", "ceremonial", "military"],
+ "moji": "💂"
+ },
+ "guitar": {
+ "unicode": "1F3B8",
+ "unicode_alternates": [],
+ "name": "guitar",
+ "shortname": ":guitar:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["instrument", "music", "guitar", "string", "music", "instrument", "jam", "rock", "acoustic", "electric"],
+ "moji": "🎸"
+ },
+ "gun": {
+ "unicode": "1F52B",
+ "unicode_alternates": [],
+ "name": "pistol",
+ "shortname": ":gun:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["violence", "weapon"],
+ "moji": "🔫"
+ },
+ "haircut": {
+ "unicode": "1F487",
+ "unicode_alternates": [],
+ "name": "haircut",
+ "shortname": ":haircut:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["female", "girl", "woman"],
+ "moji": "💇"
+ },
+ "hamburger": {
+ "unicode": "1F354",
+ "unicode_alternates": [],
+ "name": "hamburger",
+ "shortname": ":hamburger:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["food", "meat", "hamburger", "burger", "meat", "cow", "beef"],
+ "moji": "🍔"
+ },
+ "hammer": {
+ "unicode": "1F528",
+ "unicode_alternates": [],
+ "name": "hammer",
+ "shortname": ":hammer:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["done", "judge", "law", "ruling", "tools", "verdict"],
+ "moji": "🔨"
+ },
+ "hamster": {
+ "unicode": "1F439",
+ "unicode_alternates": [],
+ "name": "hamster face",
+ "shortname": ":hamster:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "nature"],
+ "moji": "🐹"
+ },
+ "hand_splayed": {
+ "unicode": "1F590",
+ "unicode_alternates": [],
+ "name": "raised hand with fingers splayed",
+ "shortname": ":hand_splayed:",
+ "category": "people",
+ "aliases": [":raised_hand_with_fingers_splayed:"],
+ "aliases_ascii": [],
+ "keywords": ["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:"],
+ "aliases_ascii": [],
+ "keywords": ["hi", "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"]
+ },
+ "handbag": {
+ "unicode": "1F45C",
+ "unicode_alternates": [],
+ "name": "handbag",
+ "shortname": ":handbag:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["accessories", "accessory", "bag", "fashion"],
+ "moji": "👜"
+ },
+ "hard_disk": {
+ "unicode": "1F5B4",
+ "unicode_alternates": [],
+ "name": "hard disk",
+ "shortname": ":hard_disk:",
+ "category": "objects_symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["save", "technology", "storage", "information", "computer", "drive", "megabyte", "gigabyte", "hd"]
+ },
+ "hash": {
+ "moji": "#⃣",
+ "unicode": "0023-20E3",
+ "unicode_alternates": ["0023-FE0F-20E3"],
+ "name": "number sign",
+ "shortname": ":hash:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["symbol"]
+ },
+ "hatched_chick": {
+ "unicode": "1F425",
+ "unicode_alternates": [],
+ "name": "front-facing baby chick",
+ "shortname": ":hatched_chick:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["baby", "chicken", "chick", "baby", "bird", "chicken", "young", "woman", "cute"],
+ "moji": "🐥"
+ },
+ "hatching_chick": {
+ "unicode": "1F423",
+ "unicode_alternates": [],
+ "name": "hatching chick",
+ "shortname": ":hatching_chick:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["born", "chicken", "egg", "chick", "egg", "baby", "bird", "chicken", "young", "woman", "cute"],
+ "moji": "🐣"
+ },
+ "headphones": {
+ "unicode": "1F3A7",
+ "unicode_alternates": [],
+ "name": "headphone",
+ "shortname": ":headphones:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["gadgets", "music", "score", "headphone", "sound", "music", "ears", "beats", "buds", "audio", "listen"],
+ "moji": "🎧"
+ },
+ "hear_no_evil": {
+ "unicode": "1F649",
+ "unicode_alternates": [],
+ "name": "hear-no-evil monkey",
+ "shortname": ":hear_no_evil:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "monkey", "monkey", "ears", "hear", "sound", "kikazaru"],
+ "moji": "🙉"
+ },
+ "heart": {
+ "moji": "❤",
+ "unicode": "2764",
+ "unicode_alternates": ["2764-FE0F"],
+ "name": "heavy black heart",
+ "shortname": ":heart:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": ["<3"],
+ "keywords": ["like", "love", "red", "pink", "black", "heart", "love", "passion", "romance", "intense", "desire", "death", "evil", "cold", "valentines"]
+ },
+ "heart_decoration": {
+ "unicode": "1F49F",
+ "unicode_alternates": [],
+ "name": "heart decoration",
+ "shortname": ":heart_decoration:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["like", "love", "purple-square"],
+ "moji": "💟"
+ },
+ "heart_eyes": {
+ "unicode": "1F60D",
+ "unicode_alternates": [],
+ "name": "smiling face with heart-shaped eyes",
+ "shortname": ":heart_eyes:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["affection", "crush", "face", "infatuation", "like", "love", "valentines", "smiling", "heart", "lovestruck", "love", "flirt", "smile", "heart-shaped"],
+ "moji": "😍"
+ },
+ "heart_eyes_cat": {
+ "unicode": "1F63B",
+ "unicode_alternates": [],
+ "name": "smiling cat face with heart-shaped eyes",
+ "shortname": ":heart_eyes_cat:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["affection", "animal", "cats", "like", "love", "valentines", "lovestruck", "love", "heart"],
+ "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",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["affection", "like", "love", "valentines"],
+ "moji": "💓"
+ },
+ "heartpulse": {
+ "unicode": "1F497",
+ "unicode_alternates": [],
+ "name": "growing heart",
+ "shortname": ":heartpulse:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["affection", "like", "love", "valentines"],
+ "moji": "💗"
+ },
+ "hearts": {
+ "unicode": "2665",
+ "unicode_alternates": ["2665-FE0F"],
+ "name": "black heart suit",
+ "shortname": ":hearts:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["cards", "poker"],
+ "moji": "♥"
+ },
+ "heavy_check_mark": {
+ "unicode": "2714",
+ "unicode_alternates": ["2714-FE0F"],
+ "name": "heavy check mark",
+ "shortname": ":heavy_check_mark:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["nike", "ok"],
+ "moji": "✔"
+ },
+ "heavy_division_sign": {
+ "unicode": "2797",
+ "unicode_alternates": [],
+ "name": "heavy division sign",
+ "shortname": ":heavy_division_sign:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["calculation", "divide", "math"],
+ "moji": "➗"
+ },
+ "heavy_dollar_sign": {
+ "unicode": "1F4B2",
+ "unicode_alternates": [],
+ "name": "heavy dollar sign",
+ "shortname": ":heavy_dollar_sign:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["currency", "money", "payment", "dollar", "currency", "money", "cash", "sale", "purchase", "value"],
+ "moji": "💲"
+ },
+ "heavy_minus_sign": {
+ "unicode": "2796",
+ "unicode_alternates": [],
+ "name": "heavy minus sign",
+ "shortname": ":heavy_minus_sign:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["calculation", "math"],
+ "moji": "➖"
+ },
+ "heavy_multiplication_x": {
+ "unicode": "2716",
+ "unicode_alternates": ["2716-FE0F"],
+ "name": "heavy multiplication x",
+ "shortname": ":heavy_multiplication_x:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["calculation", "math"],
+ "moji": "✖"
+ },
+ "heavy_plus_sign": {
+ "unicode": "2795",
+ "unicode_alternates": [],
+ "name": "heavy plus sign",
+ "shortname": ":heavy_plus_sign:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["calculation", "math"],
+ "moji": "➕"
+ },
+ "helicopter": {
+ "unicode": "1F681",
+ "unicode_alternates": [],
+ "name": "helicopter",
+ "shortname": ":helicopter:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["transportation", "vehicle", "helicopter", "helo", "gyro", "gyrocopter"],
+ "moji": "🚁"
+ },
+ "herb": {
+ "unicode": "1F33F",
+ "unicode_alternates": [],
+ "name": "herb",
+ "shortname": ":herb:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["grass", "lawn", "medicine", "plant", "vegetable", "weed", "herb", "spice", "plant", "cook", "cooking"],
+ "moji": "🌿"
+ },
+ "hibiscus": {
+ "unicode": "1F33A",
+ "unicode_alternates": [],
+ "name": "hibiscus",
+ "shortname": ":hibiscus:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["flowers", "plant", "vegetable", "hibiscus", "flower", "warm"],
+ "moji": "🌺"
+ },
+ "high_brightness": {
+ "unicode": "1F506",
+ "unicode_alternates": [],
+ "name": "high brightness symbol",
+ "shortname": ":high_brightness:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["light", "summer", "sun"],
+ "moji": "🔆"
+ },
+ "high_heel": {
+ "unicode": "1F460",
+ "unicode_alternates": [],
+ "name": "high-heeled shoe",
+ "shortname": ":high_heel:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["fashion", "female", "shoes"],
+ "moji": "👠"
+ },
+ "hole": {
+ "unicode": "1F573",
+ "unicode_alternates": [],
+ "name": "hole",
+ "shortname": ":hole:",
+ "category": "objects_symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["pit", "well"]
+ },
+ "homes": {
+ "unicode": "1F3D8",
+ "unicode_alternates": [],
+ "name": "house buildings",
+ "shortname": ":homes:",
+ "category": "travel_places",
+ "aliases": [":house_buildings:"],
+ "aliases_ascii": [],
+ "keywords": ["home", "residence", "dwelling", "mansion", "bungalow", "ranch", "craftsman"]
+ },
+ "honey_pot": {
+ "unicode": "1F36F",
+ "unicode_alternates": [],
+ "name": "honey pot",
+ "shortname": ":honey_pot:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["bees", "sweet", "honey", "pot", "bees", "pooh", "bear"],
+ "moji": "🍯"
+ },
+ "horse": {
+ "unicode": "1F434",
+ "unicode_alternates": [],
+ "name": "horse face",
+ "shortname": ":horse:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "brown"],
+ "moji": "🐴"
+ },
+ "horse_racing": {
+ "unicode": "1F3C7",
+ "unicode_alternates": [],
+ "name": "horse racing",
+ "shortname": ":horse_racing:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "betting", "competition", "horse", "race", "racing", "jockey", "triple crown"],
+ "moji": "🏇"
+ },
+ "hospital": {
+ "unicode": "1F3E5",
+ "unicode_alternates": [],
+ "name": "hospital",
+ "shortname": ":hospital:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["building", "doctor", "health", "surgery"],
+ "moji": "🏥"
+ },
+ "hot_pepper": {
+ "unicode": "1F336",
+ "unicode_alternates": [],
+ "name": "hot pepper",
+ "shortname": ":hot_pepper:",
+ "category": "food_drink",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["food", "nature", "spicy", "chili", "cayenne", "habanero", "jalapeno"]
+ },
+ "hotel": {
+ "unicode": "1F3E8",
+ "unicode_alternates": [],
+ "name": "hotel",
+ "shortname": ":hotel:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["accomodation", "building", "checkin", "whotel", "hotel", "motel", "holiday inn", "hospital"],
+ "moji": "🏨"
+ },
+ "hotsprings": {
+ "unicode": "2668",
+ "unicode_alternates": ["2668-FE0F"],
+ "name": "hot springs",
+ "shortname": ":hotsprings:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["bath", "relax", "warm"],
+ "moji": "♨"
+ },
+ "hourglass": {
+ "unicode": "231B",
+ "unicode_alternates": ["231B-FE0F"],
+ "name": "hourglass",
+ "shortname": ":hourglass:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["clock", "oldschool", "time"],
+ "moji": "⌛"
+ },
+ "hourglass_flowing_sand": {
+ "unicode": "23F3",
+ "unicode_alternates": [],
+ "name": "hourglass with flowing sand",
+ "shortname": ":hourglass_flowing_sand:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["countdown", "oldschool", "time"],
+ "moji": "⏳"
+ },
+ "house": {
+ "unicode": "1F3E0",
+ "unicode_alternates": [],
+ "name": "house building",
+ "shortname": ":house:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["building", "home", "house", "home", "residence", "dwelling", "mansion", "bungalow", "ranch", "craftsman"],
+ "moji": "🏠"
+ },
+ "house_abandoned": {
+ "unicode": "1F3DA",
+ "unicode_alternates": [],
+ "name": "derelict house building",
+ "shortname": ":house_abandoned:",
+ "category": "travel_places",
+ "aliases": [":derelict_house_building:"],
+ "aliases_ascii": [],
+ "keywords": ["home", "residence", "dwelling", "mansion", "bungalow", "ranch", "craftsman", "boarded", "abandoned", "vacant", "run down", "shoddy"]
+ },
+ "house_with_garden": {
+ "unicode": "1F3E1",
+ "unicode_alternates": [],
+ "name": "house with garden",
+ "shortname": ":house_with_garden:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["home", "nature", "plant"],
+ "moji": "🏡"
+ },
+ "hushed": {
+ "unicode": "1F62F",
+ "unicode_alternates": [],
+ "name": "hushed face",
+ "shortname": ":hushed:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["face", "woo", "quiet", "hush", "whisper", "silent"],
+ "moji": "😯"
+ },
+ "ice_cream": {
+ "unicode": "1F368",
+ "unicode_alternates": [],
+ "name": "ice cream",
+ "shortname": ":ice_cream:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["desert", "food", "hot", "icecream", "ice", "cream", "dairy", "dessert", "cold", "soft", "serve", "cone", "waffle"],
+ "moji": "🍨"
+ },
+ "icecream": {
+ "unicode": "1F366",
+ "unicode_alternates": [],
+ "name": "soft ice cream",
+ "shortname": ":icecream:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["desert", "food", "hot", "icecream", "ice", "cream", "dairy", "dessert", "cold", "soft", "serve", "cone", "yogurt"],
+ "moji": "🍦"
+ },
+ "ideograph_advantage": {
+ "unicode": "1F250",
+ "unicode_alternates": [],
+ "name": "circled ideograph advantage",
+ "shortname": ":ideograph_advantage:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["chinese", "get", "kanji", "obtain"],
+ "moji": "🉐"
+ },
+ "imp": {
+ "unicode": "1F47F",
+ "unicode_alternates": [],
+ "name": "imp",
+ "shortname": ":imp:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["angry", "devil", "evil", "horns", "cute", "devil"],
+ "moji": "👿"
+ },
+ "inbox_tray": {
+ "unicode": "1F4E5",
+ "unicode_alternates": [],
+ "name": "inbox tray",
+ "shortname": ":inbox_tray:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["documents", "email"],
+ "moji": "📥"
+ },
+ "incoming_envelope": {
+ "unicode": "1F4E8",
+ "unicode_alternates": [],
+ "name": "incoming envelope",
+ "shortname": ":incoming_envelope:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["email", "inbox"],
+ "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",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["female", "girl", "human", "woman", "information", "help", "question", "answer", "sassy", "unimpressed", "attitude", "snarky"],
+ "moji": "💁"
+ },
+ "information_source": {
+ "unicode": "2139",
+ "unicode_alternates": ["2139-FE0F"],
+ "name": "information source",
+ "shortname": ":information_source:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["alphabet", "blue-square", "letter"],
+ "moji": "ℹ"
+ },
+ "innocent": {
+ "unicode": "1F607",
+ "unicode_alternates": [],
+ "name": "smiling face with halo",
+ "shortname": ":innocent:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": ["O:-)", "0:-3", "0:3", "0:-)", "0:)", "0;^)", "O:-)", "O:)", "O;-)", "O=)", "0;-)", "O:-3", "O:3"],
+ "keywords": ["angel", "face", "halo", "halo", "angel", "innocent", "ring", "circle", "heaven"],
+ "moji": "😇"
+ },
+ "interrobang": {
+ "unicode": "2049",
+ "unicode_alternates": ["2049-FE0F"],
+ "name": "exclamation question mark",
+ "shortname": ":interrobang:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["punctuation", "surprise", "wat"],
+ "moji": "⁉"
+ },
+ "iphone": {
+ "unicode": "1F4F1",
+ "unicode_alternates": [],
+ "name": "mobile phone",
+ "shortname": ":iphone:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["apple", "dial", "gadgets", "technology"],
+ "moji": "📱"
+ },
+ "island": {
+ "unicode": "1F3DD",
+ "unicode_alternates": [],
+ "name": "desert island",
+ "shortname": ":island:",
+ "category": "travel_places",
+ "aliases": [":desert_island:"],
+ "aliases_ascii": [],
+ "keywords": ["land", "solitude", "alone"]
+ },
+ "izakaya_lantern": {
+ "unicode": "1F3EE",
+ "unicode_alternates": [],
+ "name": "izakaya lantern",
+ "shortname": ":izakaya_lantern:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["light", "izakaya", "lantern", "stay", "drink", "alcohol", "bar", "sake", "restaurant"],
+ "moji": "🏮"
+ },
+ "jack_o_lantern": {
+ "unicode": "1F383",
+ "unicode_alternates": [],
+ "name": "jack-o-lantern",
+ "shortname": ":jack_o_lantern:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["halloween", "jack-o-lantern", "pumpkin", "halloween", "holiday", "carve", "autumn", "fall", "october", "saints", "costume", "spooky", "horror", "scary", "scared", "dead"],
+ "moji": "🎃"
+ },
+ "japan": {
+ "unicode": "1F5FE",
+ "unicode_alternates": [],
+ "name": "silhouette of japan",
+ "shortname": ":japan:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["nation"],
+ "moji": "🗾"
+ },
+ "japanese_castle": {
+ "unicode": "1F3EF",
+ "unicode_alternates": [],
+ "name": "japanese castle",
+ "shortname": ":japanese_castle:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["building", "photo", "castle", "japanese", "residence", "royalty", "fort", "fortified", "fortress"],
+ "moji": "🏯"
+ },
+ "japanese_goblin": {
+ "unicode": "1F47A",
+ "unicode_alternates": [],
+ "name": "japanese goblin",
+ "shortname": ":japanese_goblin:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["evil", "mask", "red", "japanese", "tengu", "supernatural", "avian", "demon", "goblin", "mask", "theater", "nose", "frown", "mustache", "anger", "frustration"],
+ "moji": "👺"
+ },
+ "japanese_ogre": {
+ "unicode": "1F479",
+ "unicode_alternates": [],
+ "name": "japanese ogre",
+ "shortname": ":japanese_ogre:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["monster", "japanese", "oni", "demon", "troll", "ogre", "folklore", "monster", "devil", "mask", "theater", "horns", "teeth"],
+ "moji": "👹"
+ },
+ "jeans": {
+ "unicode": "1F456",
+ "unicode_alternates": [],
+ "name": "jeans",
+ "shortname": ":jeans:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["fashion", "shopping", "jeans", "pants", "blue", "denim", "levi&#039;s", "levi", "designer", "work", "skinny"],
+ "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",
+ "aliases": [],
+ "aliases_ascii": [":')", ":'-)"],
+ "keywords": ["cry", "face", "haha", "happy", "tears", "tears", "cry", "joy", "happy", "weep"],
+ "moji": "😂"
+ },
+ "joy_cat": {
+ "unicode": "1F639",
+ "unicode_alternates": [],
+ "name": "cat face with tears of joy",
+ "shortname": ":joy_cat:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "cats", "haha", "happy", "tears", "happy", "tears", "cry", "joy"],
+ "moji": "😹"
+ },
+ "joystick": {
+ "unicode": "1F579",
+ "unicode_alternates": [],
+ "name": "joystick",
+ "shortname": ":joystick:",
+ "category": "objects_symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["games", "atari", "controller"]
+ },
+ "key": {
+ "unicode": "1F511",
+ "unicode_alternates": [],
+ "name": "key",
+ "shortname": ":key:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["door", "lock", "password"],
+ "moji": "🔑"
+ },
+ "key2": {
+ "unicode": "1F5DD",
+ "unicode_alternates": [],
+ "name": "old key",
+ "shortname": ":key2:",
+ "category": "objects_symbols",
+ "aliases": [":old_key:"],
+ "aliases_ascii": [],
+ "keywords": ["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:"],
+ "aliases_ascii": [],
+ "keywords": ["computer", "input", "desktop"]
+ },
+ "keyboard_with_jacks": {
+ "unicode": "1F398",
+ "unicode_alternates": [],
+ "name": "musical keyboard with jacks",
+ "shortname": ":keyboard_with_jacks:",
+ "category": "objects_symbols",
+ "aliases": [":musical_keyboard_with_jacks:"],
+ "aliases_ascii": [],
+ "keywords": ["music", "instrument", "midi"]
+ },
+ "keycap_ten": {
+ "unicode": "1F51F",
+ "unicode_alternates": [],
+ "name": "keycap ten",
+ "shortname": ":keycap_ten:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["10", "blue-square", "numbers"],
+ "moji": "🔟"
+ },
+ "kimono": {
+ "unicode": "1F458",
+ "unicode_alternates": [],
+ "name": "kimono",
+ "shortname": ":kimono:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["dress", "fashion", "female", "japanese", "women"],
+ "moji": "👘"
+ },
+ "kiss": {
+ "unicode": "1F48B",
+ "unicode_alternates": [],
+ "name": "kiss mark",
+ "shortname": ":kiss:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["affection", "face", "like", "lips", "love", "valentines"],
+ "moji": "💋"
+ },
+ "kiss_mm": {
+ "unicode": "1F468-2764-1F48B-1F468",
+ "unicode_alternates": ["1F468-200D-2764-FE0F-200D-1F48B-200D-1F468"],
+ "name": "kiss (man,man)",
+ "shortname": ":kiss_mm:",
+ "category": "people",
+ "aliases": [":couplekiss_mm:"],
+ "aliases_ascii": [],
+ "keywords": ["dating", "like", "love", "marriage", "valentines", "couple"]
+ },
+ "kiss_ww": {
+ "unicode": "1F469-2764-1F48B-1F469",
+ "unicode_alternates": ["1F469-200D-2764-FE0F-200D-1F48B-200D-1F469"],
+ "name": "kiss (woman,woman)",
+ "shortname": ":kiss_ww:",
+ "category": "people",
+ "aliases": [":couplekiss_ww:"],
+ "aliases_ascii": [],
+ "keywords": ["dating", "like", "love", "marriage", "valentines", "couple"]
+ },
+ "kissing": {
+ "unicode": "1F617",
+ "unicode_alternates": [],
+ "name": "kissing face",
+ "shortname": ":kissing:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["3", "face", "infatuation", "like", "love", "valentines", "kissing", "kiss", "pucker", "lips", "smooch"],
+ "moji": "😗"
+ },
+ "kissing_cat": {
+ "unicode": "1F63D",
+ "unicode_alternates": [],
+ "name": "kissing cat face with closed eyes",
+ "shortname": ":kissing_cat:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "cats", "passion", "kiss", "puckered", "heart", "love"],
+ "moji": "😽"
+ },
+ "kissing_closed_eyes": {
+ "unicode": "1F61A",
+ "unicode_alternates": [],
+ "name": "kissing face with closed eyes",
+ "shortname": ":kissing_closed_eyes:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["affection", "face", "infatuation", "like", "love", "valentines", "kissing", "kiss", "passion", "puckered", "heart", "love", "smooch"],
+ "moji": "😚"
+ },
+ "kissing_heart": {
+ "unicode": "1F618",
+ "unicode_alternates": [],
+ "name": "face throwing a kiss",
+ "shortname": ":kissing_heart:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [":*", ":-*", "=*", ":^*"],
+ "keywords": ["affection", "face", "infatuation", "kiss", "blowing kiss", "heart", "love", "lips", "like", "love", "valentines"],
+ "moji": "😘"
+ },
+ "kissing_smiling_eyes": {
+ "unicode": "1F619",
+ "unicode_alternates": [],
+ "name": "kissing face with smiling eyes",
+ "shortname": ":kissing_smiling_eyes:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["affection", "face", "infatuation", "valentines", "kissing", "kiss", "smile", "pucker", "lips", "smooch"],
+ "moji": "😙"
+ },
+ "knife": {
+ "unicode": "1F52A",
+ "unicode_alternates": [],
+ "name": "hocho",
+ "shortname": ":knife:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🔪"
+ },
+ "koala": {
+ "unicode": "1F428",
+ "unicode_alternates": [],
+ "name": "koala",
+ "shortname": ":koala:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "nature"],
+ "moji": "🐨"
+ },
+ "koko": {
+ "unicode": "1F201",
+ "unicode_alternates": [],
+ "name": "squared katakana koko",
+ "shortname": ":koko:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blue-square", "destination", "here", "japanese", "katakana"],
+ "moji": "🈁"
+ },
+ "label": {
+ "unicode": "1F3F7",
+ "unicode_alternates": [],
+ "name": "label",
+ "shortname": ":label:",
+ "category": "objects_symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["tag"]
+ },
+ "large_blue_circle": {
+ "unicode": "1F535",
+ "unicode_alternates": [],
+ "name": "large blue circle",
+ "shortname": ":large_blue_circle:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🔵"
+ },
+ "large_blue_diamond": {
+ "unicode": "1F537",
+ "unicode_alternates": [],
+ "name": "large blue diamond",
+ "shortname": ":large_blue_diamond:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["shape"],
+ "moji": "🔷"
+ },
+ "large_orange_diamond": {
+ "unicode": "1F536",
+ "unicode_alternates": [],
+ "name": "large orange diamond",
+ "shortname": ":large_orange_diamond:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["shape"],
+ "moji": "🔶"
+ },
+ "last_quarter_moon": {
+ "unicode": "1F317",
+ "unicode_alternates": [],
+ "name": "last quarter moon symbol",
+ "shortname": ":last_quarter_moon:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["nature", "moon", "last", "quarter", "sky", "night", "cheese", "phase"],
+ "moji": "🌗"
+ },
+ "last_quarter_moon_with_face": {
+ "unicode": "1F31C",
+ "unicode_alternates": [],
+ "name": "last quarter moon with face",
+ "shortname": ":last_quarter_moon_with_face:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["nature", "moon", "last", "quarter", "anthropomorphic", "face", "sky", "night", "cheese", "phase"],
+ "moji": "🌜"
+ },
+ "laughing": {
+ "unicode": "1F606",
+ "unicode_alternates": [],
+ "name": "smiling face with open mouth and tightly-closed ey",
+ "shortname": ":laughing:",
+ "category": "emoticons",
+ "aliases": [":satisfied:"],
+ "aliases_ascii": [">:)", ">;)", ">:-)", ">=)"],
+ "keywords": ["happy", "joy", "lol", "smiling", "laughing", "laugh"],
+ "moji": "😆"
+ },
+ "leaves": {
+ "unicode": "1F343",
+ "unicode_alternates": [],
+ "name": "leaf fluttering in wind",
+ "shortname": ":leaves:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["grass", "lawn", "nature", "plant", "tree", "vegetable", "leaves", "leaf", "wind", "float", "fluttering"],
+ "moji": "🍃"
+ },
+ "ledger": {
+ "unicode": "1F4D2",
+ "unicode_alternates": [],
+ "name": "ledger",
+ "shortname": ":ledger:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["notes", "paper"],
+ "moji": "📒"
+ },
+ "left_luggage": {
+ "unicode": "1F6C5",
+ "unicode_alternates": [],
+ "name": "left luggage",
+ "shortname": ":left_luggage:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blue-square", "travel", "bag", "baggage", "luggage", "travel"],
+ "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": ["2194-FE0F"],
+ "name": "left right arrow",
+ "shortname": ":left_right_arrow:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["shape"],
+ "moji": "↔"
+ },
+ "leftwards_arrow_with_hook": {
+ "unicode": "21A9",
+ "unicode_alternates": ["21A9-FE0F"],
+ "name": "leftwards arrow with hook",
+ "shortname": ":leftwards_arrow_with_hook:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "↩"
+ },
+ "lemon": {
+ "unicode": "1F34B",
+ "unicode_alternates": [],
+ "name": "lemon",
+ "shortname": ":lemon:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["fruit", "nature", "lemon", "yellow", "citrus"],
+ "moji": "🍋"
+ },
+ "leo": {
+ "unicode": "264C",
+ "unicode_alternates": ["264C-FE0F"],
+ "name": "leo",
+ "shortname": ":leo:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["leo", "lion", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "purple-square", "sign", "zodiac", "horoscope"],
+ "moji": "♌"
+ },
+ "leopard": {
+ "unicode": "1F406",
+ "unicode_alternates": [],
+ "name": "leopard",
+ "shortname": ":leopard:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "nature", "leopard", "cat", "spot", "spotted", "sexy"],
+ "moji": "🐆"
+ },
+ "level_slider": {
+ "unicode": "1F39A",
+ "unicode_alternates": [],
+ "name": "level slider",
+ "shortname": ":level_slider:",
+ "category": "objects_symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["controls"]
+ },
+ "levitate": {
+ "unicode": "1F574",
+ "unicode_alternates": [],
+ "name": "man in business suit levitating",
+ "shortname": ":levitate:",
+ "category": "people",
+ "aliases": [":man_in_business_suit_levitating:"],
+ "aliases_ascii": [],
+ "keywords": ["hover", "exclamation"]
+ },
+ "libra": {
+ "unicode": "264E",
+ "unicode_alternates": ["264E-FE0F"],
+ "name": "libra",
+ "shortname": ":libra:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["libra", "scales", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "purple-square", "sign", "zodiac", "horoscope"],
+ "moji": "♎"
+ },
+ "lifter": {
+ "unicode": "1F3CB",
+ "unicode_alternates": [],
+ "name": "weight lifter",
+ "shortname": ":lifter:",
+ "category": "activity",
+ "aliases": [":weight_lifter:"],
+ "aliases_ascii": [],
+ "keywords": ["bench", "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"]
+ },
+ "light_rail": {
+ "unicode": "1F688",
+ "unicode_alternates": [],
+ "name": "light rail",
+ "shortname": ":light_rail:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["transportation", "vehicle", "train", "rail", "light"],
+ "moji": "🚈"
+ },
+ "link": {
+ "unicode": "1F517",
+ "unicode_alternates": [],
+ "name": "link symbol",
+ "shortname": ":link:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["rings", "url"],
+ "moji": "🔗"
+ },
+ "lips": {
+ "unicode": "1F444",
+ "unicode_alternates": [],
+ "name": "mouth",
+ "shortname": ":lips:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["kiss", "mouth"],
+ "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",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["fashion", "female", "girl"],
+ "moji": "💄"
+ },
+ "lock": {
+ "unicode": "1F512",
+ "unicode_alternates": [],
+ "name": "lock",
+ "shortname": ":lock:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["password", "security"],
+ "moji": "🔒"
+ },
+ "lock_with_ink_pen": {
+ "unicode": "1F50F",
+ "unicode_alternates": [],
+ "name": "lock with ink pen",
+ "shortname": ":lock_with_ink_pen:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["secret", "security"],
+ "moji": "🔏"
+ },
+ "lollipop": {
+ "unicode": "1F36D",
+ "unicode_alternates": [],
+ "name": "lollipop",
+ "shortname": ":lollipop:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["candy", "food", "snack", "sweet", "lollipop", "stick", "lick", "sweet", "sugar", "candy"],
+ "moji": "🍭"
+ },
+ "loop": {
+ "unicode": "27BF",
+ "unicode_alternates": [],
+ "name": "double curly loop",
+ "shortname": ":loop:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["curly"],
+ "moji": "➿"
+ },
+ "loud_sound": {
+ "unicode": "1F50A",
+ "unicode_alternates": [],
+ "name": "speaker with three sound waves",
+ "shortname": ":loud_sound:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": [],
+ "moji": "🔊"
+ },
+ "loudspeaker": {
+ "unicode": "1F4E2",
+ "unicode_alternates": [],
+ "name": "public address loudspeaker",
+ "shortname": ":loudspeaker:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["sound", "volume"],
+ "moji": "📢"
+ },
+ "love_hotel": {
+ "unicode": "1F3E9",
+ "unicode_alternates": [],
+ "name": "love hotel",
+ "shortname": ":love_hotel:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["affection", "dating", "like", "love", "hotel", "love", "sex", "romance", "leisure", "adultery", "prostitution", "hospital", "birth", "happy"],
+ "moji": "🏩"
+ },
+ "love_letter": {
+ "unicode": "1F48C",
+ "unicode_alternates": [],
+ "name": "love letter",
+ "shortname": ":love_letter:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["affection", "email", "envelope", "like", "valentines", "love", "letter", "kiss", "heart"],
+ "moji": "💌"
+ },
+ "low_brightness": {
+ "unicode": "1F505",
+ "unicode_alternates": [],
+ "name": "low brightness symbol",
+ "shortname": ":low_brightness:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["summer", "sun"],
+ "moji": "🔅"
+ },
+ "m": {
+ "unicode": "24C2",
+ "unicode_alternates": ["24C2-FE0F"],
+ "name": "circled latin capital letter m",
+ "shortname": ":m:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["alphabet", "blue-circle", "letter"],
+ "moji": "Ⓜ"
+ },
+ "mag": {
+ "unicode": "1F50D",
+ "unicode_alternates": [],
+ "name": "left-pointing magnifying glass",
+ "shortname": ":mag:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["search", "zoom", "detective", "investigator", "detail", "details"],
+ "moji": "🔍"
+ },
+ "mag_right": {
+ "unicode": "1F50E",
+ "unicode_alternates": [],
+ "name": "right-pointing magnifying glass",
+ "shortname": ":mag_right:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["search", "zoom", "detective", "investigator", "detail", "details"],
+ "moji": "🔎"
+ },
+ "mahjong": {
+ "unicode": "1F004",
+ "unicode_alternates": ["1F004-FE0F"],
+ "name": "mahjong tile red dragon",
+ "shortname": ":mahjong:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["chinese", "game", "kanji"],
+ "moji": "🀄"
+ },
+ "mailbox": {
+ "unicode": "1F4EB",
+ "unicode_alternates": [],
+ "name": "closed mailbox with raised flag",
+ "shortname": ":mailbox:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["communication", "email", "inbox"],
+ "moji": "📫"
+ },
+ "mailbox_closed": {
+ "unicode": "1F4EA",
+ "unicode_alternates": [],
+ "name": "closed mailbox with lowered flag",
+ "shortname": ":mailbox_closed:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["communication", "email", "inbox"],
+ "moji": "📪"
+ },
+ "mailbox_with_mail": {
+ "unicode": "1F4EC",
+ "unicode_alternates": [],
+ "name": "open mailbox with raised flag",
+ "shortname": ":mailbox_with_mail:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["communication", "email", "inbox"],
+ "moji": "📬"
+ },
+ "mailbox_with_no_mail": {
+ "unicode": "1F4ED",
+ "unicode_alternates": [],
+ "name": "open mailbox with lowered flag",
+ "shortname": ":mailbox_with_no_mail:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["email", "inbox"],
+ "moji": "📭"
+ },
+ "man": {
+ "unicode": "1F468",
+ "unicode_alternates": [],
+ "name": "man",
+ "shortname": ":man:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["classy", "dad", "father", "guy", "mustashe"],
+ "moji": "👨"
+ },
+ "man_with_gua_pi_mao": {
+ "unicode": "1F472",
+ "unicode_alternates": [],
+ "name": "man with gua pi mao",
+ "shortname": ":man_with_gua_pi_mao:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["boy", "male", "skullcap", "chinese", "asian", "qing"],
+ "moji": "👲"
+ },
+ "man_with_turban": {
+ "unicode": "1F473",
+ "unicode_alternates": [],
+ "name": "man with turban",
+ "shortname": ":man_with_turban:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["male", "turban", "headdress", "headwear", "pagri", "india", "indian", "mummy", "wisdom", "peace"],
+ "moji": "👳"
+ },
+ "mans_shoe": {
+ "unicode": "1F45E",
+ "unicode_alternates": [],
+ "name": "mans shoe",
+ "shortname": ":mans_shoe:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["fashion", "male"],
+ "moji": "👞"
+ },
+ "map": {
+ "unicode": "1F5FA",
+ "unicode_alternates": [],
+ "name": "world map",
+ "shortname": ":map:",
+ "category": "travel_places",
+ "aliases": [":world_map:"],
+ "aliases_ascii": [],
+ "keywords": ["atlas", "earth", "cartography"]
+ },
+ "maple_leaf": {
+ "unicode": "1F341",
+ "unicode_alternates": [],
+ "name": "maple leaf",
+ "shortname": ":maple_leaf:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["canada", "nature", "plant", "vegetable", "maple", "leaf", "syrup", "canada", "tree"],
+ "moji": "🍁"
+ },
+ "mask": {
+ "unicode": "1F637",
+ "unicode_alternates": [],
+ "name": "face with medical mask",
+ "shortname": ":mask:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["face", "ill", "sick", "sick", "virus", "flu", "medical", "mask"],
+ "moji": "😷"
+ },
+ "massage": {
+ "unicode": "1F486",
+ "unicode_alternates": [],
+ "name": "face massage",
+ "shortname": ":massage:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["female", "girl", "woman"],
+ "moji": "💆"
+ },
+ "meat_on_bone": {
+ "unicode": "1F356",
+ "unicode_alternates": [],
+ "name": "meat on bone",
+ "shortname": ":meat_on_bone:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["food", "good", "meat", "bone", "animal", "cooked"],
+ "moji": "🍖"
+ },
+ "medal": {
+ "unicode": "1F3C5",
+ "unicode_alternates": [],
+ "name": "sports medal",
+ "shortname": ":medal:",
+ "category": "activity",
+ "aliases": [":sports_medal:"],
+ "aliases_ascii": [],
+ "keywords": ["award", "ceremony", "contest", "ftw", "place", "win", "first", "show", "reward", "achievement"]
+ },
+ "mega": {
+ "unicode": "1F4E3",
+ "unicode_alternates": [],
+ "name": "cheering megaphone",
+ "shortname": ":mega:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["sound", "speaker", "volume"],
+ "moji": "📣"
+ },
+ "melon": {
+ "unicode": "1F348",
+ "unicode_alternates": [],
+ "name": "melon",
+ "shortname": ":melon:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["food", "fruit", "nature", "melon", "cantaloupe", "honeydew"],
+ "moji": "🍈"
+ },
+ "mens": {
+ "unicode": "1F6B9",
+ "unicode_alternates": [],
+ "name": "mens symbol",
+ "shortname": ":mens:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["restroom", "toilet", "wc", "men", "bathroom", "restroom", "sign", "boy", "male", "avatar"],
+ "moji": "🚹"
+ },
+ "metro": {
+ "unicode": "1F687",
+ "unicode_alternates": [],
+ "name": "metro",
+ "shortname": ":metro:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blue-square", "mrt", "transportation", "tube", "underground", "metro", "subway", "underground", "train"],
+ "moji": "🚇"
+ },
+ "microphone": {
+ "unicode": "1F3A4",
+ "unicode_alternates": [],
+ "name": "microphone",
+ "shortname": ":microphone:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["PA", "music", "sound", "microphone", "mic", "audio", "sound", "voice", "karaoke"],
+ "moji": "🎤"
+ },
+ "microphone2": {
+ "unicode": "1F399",
+ "unicode_alternates": [],
+ "name": "studio microphone",
+ "shortname": ":microphone2:",
+ "category": "objects_symbols",
+ "aliases": [":studio_microphone:"],
+ "aliases_ascii": [],
+ "keywords": ["mic", "audio", "recording"]
+ },
+ "microscope": {
+ "unicode": "1F52C",
+ "unicode_alternates": [],
+ "name": "microscope",
+ "shortname": ":microscope:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["experiment", "laboratory", "zoomin"],
+ "moji": "🔬"
+ },
+ "middle_finger": {
+ "unicode": "1F595",
+ "unicode_alternates": [],
+ "name": "reversed hand with middle finger extended",
+ "shortname": ":middle_finger:",
+ "category": "people",
+ "aliases": [":reversed_hand_with_middle_finger_extended:"],
+ "aliases_ascii": [],
+ "keywords": ["fu"]
+ },
+ "military_medal": {
+ "unicode": "1F396",
+ "unicode_alternates": [],
+ "name": "military medal",
+ "shortname": ":military_medal:",
+ "category": "celebration",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["honor", "acknowledgment", "purple heart", "heroism", "veteran"]
+ },
+ "milky_way": {
+ "unicode": "1F30C",
+ "unicode_alternates": [],
+ "name": "milky way",
+ "shortname": ":milky_way:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["photo", "space", "milky", "galaxy", "star", "stars", "planets", "space", "sky"],
+ "moji": "🌌"
+ },
+ "minibus": {
+ "unicode": "1F690",
+ "unicode_alternates": [],
+ "name": "minibus",
+ "shortname": ":minibus:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["car", "transportation", "vehicle", "bus", "city", "transport", "transportation"],
+ "moji": "🚐"
+ },
+ "minidisc": {
+ "unicode": "1F4BD",
+ "unicode_alternates": [],
+ "name": "minidisc",
+ "shortname": ":minidisc:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["data", "disc", "disk", "record", "technology"],
+ "moji": "💽"
+ },
+ "mobile_phone_off": {
+ "unicode": "1F4F4",
+ "unicode_alternates": [],
+ "name": "mobile phone off",
+ "shortname": ":mobile_phone_off:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["mute"],
+ "moji": "📴"
+ },
+ "money_with_wings": {
+ "unicode": "1F4B8",
+ "unicode_alternates": [],
+ "name": "money with wings",
+ "shortname": ":money_with_wings:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["bills", "dollar", "payment", "money", "wings", "easy", "spend", "work", "lost", "blown", "burned", "gift", "cash", "dollar"],
+ "moji": "💸"
+ },
+ "moneybag": {
+ "unicode": "1F4B0",
+ "unicode_alternates": [],
+ "name": "money bag",
+ "shortname": ":moneybag:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["coins", "dollar", "payment"],
+ "moji": "💰"
+ },
+ "monkey": {
+ "unicode": "1F412",
+ "unicode_alternates": [],
+ "name": "monkey",
+ "shortname": ":monkey:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "nature", "monkey", "primate", "banana", "silly"],
+ "moji": "🐒"
+ },
+ "monkey_face": {
+ "unicode": "1F435",
+ "unicode_alternates": [],
+ "name": "monkey face",
+ "shortname": ":monkey_face:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "nature"],
+ "moji": "🐵"
+ },
+ "monorail": {
+ "unicode": "1F69D",
+ "unicode_alternates": [],
+ "name": "monorail",
+ "shortname": ":monorail:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["transportation", "vehicle", "train", "mono", "rail", "transport"],
+ "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",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["cap", "college", "degree", "graduation", "hat", "school", "university", "graduation", "cap", "mortarboard", "academic", "education", "ceremony", "square", "tassel"],
+ "moji": "🎓"
+ },
+ "motorboat": {
+ "unicode": "1F6E5",
+ "unicode_alternates": [],
+ "name": "motorboat",
+ "shortname": ":motorboat:",
+ "category": "travel_places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["transportation", "vehicle", "boat", "speedboat", "powerboat"]
+ },
+ "motorcycle": {
+ "unicode": "1F3CD",
+ "unicode_alternates": [],
+ "name": "racing motorcycle",
+ "shortname": ":motorcycle:",
+ "category": "activity",
+ "aliases": [":racing_motorcycle:"],
+ "aliases_ascii": [],
+ "keywords": ["bike", "speed"]
+ },
+ "motorway": {
+ "unicode": "1F6E3",
+ "unicode_alternates": [],
+ "name": "motorway",
+ "shortname": ":motorway:",
+ "category": "travel_places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["road", "highway", "freeway", "traffic", "travel"]
+ },
+ "mount_fuji": {
+ "unicode": "1F5FB",
+ "unicode_alternates": [],
+ "name": "mount fuji",
+ "shortname": ":mount_fuji:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["japan", "mountain", "nature", "photo"],
+ "moji": "🗻"
+ },
+ "mountain_bicyclist": {
+ "unicode": "1F6B5",
+ "unicode_alternates": [],
+ "name": "mountain bicyclist",
+ "shortname": ":mountain_bicyclist:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["human", "sports", "transportation", "bicyclist", "mountain", "bike", "pedal", "bicycle", "transportation"],
+ "moji": "🚵"
+ },
+ "mountain_cableway": {
+ "unicode": "1F6A0",
+ "unicode_alternates": [],
+ "name": "mountain cableway",
+ "shortname": ":mountain_cableway:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["transportation", "vehicle", "mountain", "cable", "rail", "train", "railway"],
+ "moji": "🚠"
+ },
+ "mountain_railway": {
+ "unicode": "1F69E",
+ "unicode_alternates": [],
+ "name": "mountain railway",
+ "shortname": ":mountain_railway:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["transportation", "mountain", "railway", "rail", "train", "transport"],
+ "moji": "🚞"
+ },
+ "mountain_snow": {
+ "unicode": "1F3D4",
+ "unicode_alternates": [],
+ "name": "snow capped mountain",
+ "shortname": ":mountain_snow:",
+ "category": "travel_places",
+ "aliases": [":snow_capped_mountain:"],
+ "aliases_ascii": [],
+ "keywords": ["cold", "elevation", "hiking", "peak"]
+ },
+ "mouse": {
+ "unicode": "1F42D",
+ "unicode_alternates": [],
+ "name": "mouse face",
+ "shortname": ":mouse:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "nature"],
+ "moji": "🐭"
+ },
+ "mouse2": {
+ "unicode": "1F401",
+ "unicode_alternates": [],
+ "name": "mouse",
+ "shortname": ":mouse2:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "nature", "mouse", "mice", "rodent"],
+ "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"]
+ },
+ "movie_camera": {
+ "unicode": "1F3A5",
+ "unicode_alternates": [],
+ "name": "movie camera",
+ "shortname": ":movie_camera:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["film", "record", "movie", "camera", "camcorder", "video", "motion", "picture"],
+ "moji": "🎥"
+ },
+ "moyai": {
+ "unicode": "1F5FF",
+ "unicode_alternates": [],
+ "name": "moyai",
+ "shortname": ":moyai:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["island", "stone"],
+ "moji": "🗿"
+ },
+ "muscle": {
+ "unicode": "1F4AA",
+ "unicode_alternates": [],
+ "name": "flexed biceps",
+ "shortname": ":muscle:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["arm", "flex", "hand", "strong", "muscle", "bicep"],
+ "moji": "💪"
+ },
+ "mushroom": {
+ "unicode": "1F344",
+ "unicode_alternates": [],
+ "name": "mushroom",
+ "shortname": ":mushroom:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["plant", "vegetable", "mushroom", "fungi", "food", "fungus"],
+ "moji": "🍄"
+ },
+ "musical_keyboard": {
+ "unicode": "1F3B9",
+ "unicode_alternates": [],
+ "name": "musical keyboard",
+ "shortname": ":musical_keyboard:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["instrument", "piano", "music", "keyboard", "piano", "organ", "instrument", "electric"],
+ "moji": "🎹"
+ },
+ "musical_note": {
+ "unicode": "1F3B5",
+ "unicode_alternates": [],
+ "name": "musical note",
+ "shortname": ":musical_note:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["score", "musical", "music", "note", "music", "sound"],
+ "moji": "🎵"
+ },
+ "musical_score": {
+ "unicode": "1F3BC",
+ "unicode_alternates": [],
+ "name": "musical score",
+ "shortname": ":musical_score:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["clef", "treble", "music", "musical", "score", "clef", "g-clef", "stave", "staff"],
+ "moji": "🎼"
+ },
+ "mute": {
+ "unicode": "1F507",
+ "unicode_alternates": [],
+ "name": "speaker with cancellation stroke",
+ "shortname": ":mute:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["sound", "volume"],
+ "moji": "🔇"
+ },
+ "nail_care": {
+ "unicode": "1F485",
+ "unicode_alternates": [],
+ "name": "nail polish",
+ "shortname": ":nail_care:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["beauty", "manicure"],
+ "moji": "💅"
+ },
+ "name_badge": {
+ "unicode": "1F4DB",
+ "unicode_alternates": [],
+ "name": "name badge",
+ "shortname": ":name_badge:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["fire", "forbid"],
+ "moji": "📛"
+ },
+ "necktie": {
+ "unicode": "1F454",
+ "unicode_alternates": [],
+ "name": "necktie",
+ "shortname": ":necktie:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["cloth", "fashion", "formal", "shirt", "suitup"],
+ "moji": "👔"
+ },
+ "negative_squared_cross_mark": {
+ "unicode": "274E",
+ "unicode_alternates": [],
+ "name": "negative squared cross mark",
+ "shortname": ":negative_squared_cross_mark:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["deny", "green-square", "no", "x"],
+ "moji": "❎"
+ },
+ "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"]
+ },
+ "neutral_face": {
+ "unicode": "1F610",
+ "unicode_alternates": [],
+ "name": "neutral face",
+ "shortname": ":neutral_face:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["face", "indifference", "neutral", "objective", "impartial", "blank"],
+ "moji": "😐"
+ },
+ "new": {
+ "unicode": "1F195",
+ "unicode_alternates": [],
+ "name": "squared new",
+ "shortname": ":new:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blue-square"],
+ "moji": "🆕"
+ },
+ "new_moon": {
+ "unicode": "1F311",
+ "unicode_alternates": [],
+ "name": "new moon symbol",
+ "shortname": ":new_moon:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["nature", "moon", "new", "sky", "night", "cheese", "phase"],
+ "moji": "🌑"
+ },
+ "new_moon_with_face": {
+ "unicode": "1F31A",
+ "unicode_alternates": [],
+ "name": "new moon with face",
+ "shortname": ":new_moon_with_face:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["nature", "moon", "new", "anthropomorphic", "face", "sky", "night", "cheese", "phase"],
+ "moji": "🌚"
+ },
+ "newspaper": {
+ "unicode": "1F4F0",
+ "unicode_alternates": [],
+ "name": "newspaper",
+ "shortname": ":newspaper:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["headline", "press"],
+ "moji": "📰"
+ },
+ "newspaper2": {
+ "unicode": "1F5DE",
+ "unicode_alternates": [],
+ "name": "rolled-up newspaper",
+ "shortname": ":newspaper2:",
+ "category": "objects_symbols",
+ "aliases": [":rolled_up_newspaper:"],
+ "aliases_ascii": [],
+ "keywords": ["headline", "press"]
+ },
+ "night_with_stars": {
+ "unicode": "1F303",
+ "unicode_alternates": [],
+ "name": "night with stars",
+ "shortname": ":night_with_stars:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["night", "star", "cloudless", "evening", "planets", "space", "sky"],
+ "moji": "🌃"
+ },
+ "nine": {
+ "moji": "9️⃣",
+ "unicode": "0039-20E3",
+ "unicode_alternates": ["0039-FE0F-20E3"],
+ "name": "digit nine",
+ "shortname": ":nine:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["9", "blue-square", "numbers"]
+ },
+ "no_bell": {
+ "unicode": "1F515",
+ "unicode_alternates": [],
+ "name": "bell with cancellation stroke",
+ "shortname": ":no_bell:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["mute", "sound", "volume"],
+ "moji": "🔕"
+ },
+ "no_bicycles": {
+ "unicode": "1F6B3",
+ "unicode_alternates": [],
+ "name": "no bicycles",
+ "shortname": ":no_bicycles:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["cyclist", "prohibited", "bicycle", "bike pedal", "no"],
+ "moji": "🚳"
+ },
+ "no_entry": {
+ "unicode": "26D4",
+ "unicode_alternates": ["26D4-FE0F"],
+ "name": "no entry",
+ "shortname": ":no_entry:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["bad", "denied", "limit", "privacy", "security", "stop"],
+ "moji": "⛔"
+ },
+ "no_entry_sign": {
+ "unicode": "1F6AB",
+ "unicode_alternates": [],
+ "name": "no entry sign",
+ "shortname": ":no_entry_sign:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["denied", "disallow", "forbid", "limit", "stop", "no", "stop", "entry"],
+ "moji": "🚫"
+ },
+ "no_good": {
+ "unicode": "1F645",
+ "unicode_alternates": [],
+ "name": "face with no good gesture",
+ "shortname": ":no_good:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["female", "girl", "woman", "no", "stop", "nope", "don&#039;t", "not"],
+ "moji": "🙅"
+ },
+ "no_mobile_phones": {
+ "unicode": "1F4F5",
+ "unicode_alternates": [],
+ "name": "no mobile phones",
+ "shortname": ":no_mobile_phones:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["iphone", "mute"],
+ "moji": "📵"
+ },
+ "no_mouth": {
+ "unicode": "1F636",
+ "unicode_alternates": [],
+ "name": "face without mouth",
+ "shortname": ":no_mouth:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [":-X", ":X", ":-#", ":#", "=X", "=x", ":x", ":-x", "=#"],
+ "keywords": ["face", "hellokitty", "mouth", "silent", "vapid"],
+ "moji": "😶"
+ },
+ "no_pedestrians": {
+ "unicode": "1F6B7",
+ "unicode_alternates": [],
+ "name": "no pedestrians",
+ "shortname": ":no_pedestrians:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["crossing", "rules", "walking", "no", "walk", "pedestrian", "stroll", "stride", "foot", "feet"],
+ "moji": "🚷"
+ },
+ "no_smoking": {
+ "unicode": "1F6AD",
+ "unicode_alternates": [],
+ "name": "no smoking symbol",
+ "shortname": ":no_smoking:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["cigarette", "no", "smoking", "cigarette", "smoke", "cancer", "lungs", "inhale", "tar", "nicotine"],
+ "moji": "🚭"
+ },
+ "non-potable_water": {
+ "unicode": "1F6B1",
+ "unicode_alternates": [],
+ "name": "non-potable water symbol",
+ "shortname": ":non-potable_water:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["drink", "faucet", "tap", "non-potable", "water", "not drinkable", "dirty", "gross", "aqua", "h20"],
+ "moji": "🚱"
+ },
+ "nose": {
+ "unicode": "1F443",
+ "unicode_alternates": [],
+ "name": "nose",
+ "shortname": ":nose:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["smell", "sniff"],
+ "moji": "👃"
+ },
+ "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"]
+ },
+ "notebook": {
+ "unicode": "1F4D3",
+ "unicode_alternates": [],
+ "name": "notebook",
+ "shortname": ":notebook:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["notes", "paper", "record", "stationery"],
+ "moji": "📓"
+ },
+ "notebook_with_decorative_cover": {
+ "unicode": "1F4D4",
+ "unicode_alternates": [],
+ "name": "notebook with decorative cover",
+ "shortname": ":notebook_with_decorative_cover:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["classroom", "notes", "paper", "record"],
+ "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",
+ "aliases": [":spiral_note_pad:"],
+ "aliases_ascii": [],
+ "keywords": ["stationery"]
+ },
+ "notes": {
+ "unicode": "1F3B6",
+ "unicode_alternates": [],
+ "name": "multiple musical notes",
+ "shortname": ":notes:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["music", "score", "musical", "music", "notes", "music", "sound", "melody"],
+ "moji": "🎶"
+ },
+ "nut_and_bolt": {
+ "unicode": "1F529",
+ "unicode_alternates": [],
+ "name": "nut and bolt",
+ "shortname": ":nut_and_bolt:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["handy", "tools"],
+ "moji": "🔩"
+ },
+ "o": {
+ "unicode": "2B55",
+ "unicode_alternates": ["2B55-FE0F"],
+ "name": "heavy large circle",
+ "shortname": ":o:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["circle", "round"],
+ "moji": "⭕"
+ },
+ "o2": {
+ "unicode": "1F17E",
+ "unicode_alternates": [],
+ "name": "negative squared latin capital letter o",
+ "shortname": ":o2:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["alphabet", "letter", "red-square"],
+ "moji": "🅾"
+ },
+ "ocean": {
+ "unicode": "1F30A",
+ "unicode_alternates": [],
+ "name": "water wave",
+ "shortname": ":ocean:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["sea", "water", "wave", "ocean", "wave", "surf", "beach", "tide"],
+ "moji": "🌊"
+ },
+ "octopus": {
+ "unicode": "1F419",
+ "unicode_alternates": [],
+ "name": "octopus",
+ "shortname": ":octopus:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "creature", "ocean", "sea"],
+ "moji": "🐙"
+ },
+ "oden": {
+ "unicode": "1F362",
+ "unicode_alternates": [],
+ "name": "oden",
+ "shortname": ":oden:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["food", "japanese", "oden", "seafood", "casserole", "stew"],
+ "moji": "🍢"
+ },
+ "office": {
+ "unicode": "1F3E2",
+ "unicode_alternates": [],
+ "name": "office building",
+ "shortname": ":office:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["building", "bureau", "work"],
+ "moji": "🏢"
+ },
+ "oil": {
+ "unicode": "1F6E2",
+ "unicode_alternates": [],
+ "name": "oil drum",
+ "shortname": ":oil:",
+ "category": "objects_symbols",
+ "aliases": [":oil_drum:"],
+ "aliases_ascii": [],
+ "keywords": ["petroleum"]
+ },
+ "ok": {
+ "unicode": "1F197",
+ "unicode_alternates": [],
+ "name": "squared ok",
+ "shortname": ":ok:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["agree", "blue-square", "good", "yes"],
+ "moji": "🆗"
+ },
+ "ok_hand": {
+ "unicode": "1F44C",
+ "unicode_alternates": [],
+ "name": "ok hand sign",
+ "shortname": ":ok_hand:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["fingers", "limbs", "perfect", "okay", "ok", "smoke", "smoking", "marijuana", "joint", "pot", "420"],
+ "moji": "👌"
+ },
+ "ok_woman": {
+ "unicode": "1F646",
+ "unicode_alternates": [],
+ "name": "face with ok gesture",
+ "shortname": ":ok_woman:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": ["*\\0/*", "\\0/", "*\\O/*", "\\O/"],
+ "keywords": ["female", "girl", "human", "pink", "women", "yes", "ok", "okay", "accept"],
+ "moji": "🙆"
+ },
+ "older_man": {
+ "unicode": "1F474",
+ "unicode_alternates": [],
+ "name": "older man",
+ "shortname": ":older_man:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["human", "male", "men"],
+ "moji": "👴"
+ },
+ "older_woman": {
+ "unicode": "1F475",
+ "unicode_alternates": [],
+ "name": "older woman",
+ "shortname": ":older_woman:",
+ "category": "emoticons",
+ "aliases": [":grandma:"],
+ "aliases_ascii": [],
+ "keywords": ["female", "girl", "women", "grandma", "grandmother"],
+ "moji": "👵"
+ },
+ "om_symbol": {
+ "unicode": "1F549",
+ "unicode_alternates": [],
+ "name": "om symbol",
+ "shortname": ":om_symbol:",
+ "category": "objects_symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["hinduism", "sound", "spiritual", "icon", "dharmic", "buddhism", "jainism", "meditate"]
+ },
+ "on": {
+ "unicode": "1F51B",
+ "unicode_alternates": [],
+ "name": "on with exclamation mark with left right arrow abo",
+ "shortname": ":on:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["arrow", "words"],
+ "moji": "🔛"
+ },
+ "oncoming_automobile": {
+ "unicode": "1F698",
+ "unicode_alternates": [],
+ "name": "oncoming automobile",
+ "shortname": ":oncoming_automobile:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["car", "transportation", "vehicle", "sedan", "car", "automobile"],
+ "moji": "🚘"
+ },
+ "oncoming_bus": {
+ "unicode": "1F68D",
+ "unicode_alternates": [],
+ "name": "oncoming bus",
+ "shortname": ":oncoming_bus:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["transportation", "vehicle", "bus", "school", "city", "transportation", "public"],
+ "moji": "🚍"
+ },
+ "oncoming_police_car": {
+ "unicode": "1F694",
+ "unicode_alternates": [],
+ "name": "oncoming police car",
+ "shortname": ":oncoming_police_car:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["enforcement", "law", "vehicle", "police", "car", "emergency", "ticket", "citation", "crime", "help", "officer"],
+ "moji": "🚔"
+ },
+ "oncoming_taxi": {
+ "unicode": "1F696",
+ "unicode_alternates": [],
+ "name": "oncoming taxi",
+ "shortname": ":oncoming_taxi:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["cars", "uber", "vehicle", "taxi", "car", "automobile", "city", "transport", "service"],
+ "moji": "🚖"
+ },
+ "one": {
+ "moji": "1️⃣",
+ "unicode": "0031-20E3",
+ "unicode_alternates": ["0031-FE0F-20E3"],
+ "name": "digit one",
+ "shortname": ":one:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["1", "blue-square", "numbers"]
+ },
+ "open_file_folder": {
+ "unicode": "1F4C2",
+ "unicode_alternates": [],
+ "name": "open file folder",
+ "shortname": ":open_file_folder:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["documents", "load"],
+ "moji": "📂"
+ },
+ "open_hands": {
+ "unicode": "1F450",
+ "unicode_alternates": [],
+ "name": "open hands sign",
+ "shortname": ":open_hands:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["butterfly", "fingers"],
+ "moji": "👐"
+ },
+ "open_mouth": {
+ "unicode": "1F62E",
+ "unicode_alternates": [],
+ "name": "face with open mouth",
+ "shortname": ":open_mouth:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [":-O", ":O", ":-o", ":o", "O_O", ">:O"],
+ "keywords": ["face", "impressed", "mouth", "open", "jaw", "gapping", "surprise", "wow"],
+ "moji": "😮"
+ },
+ "ophiuchus": {
+ "unicode": "26CE",
+ "unicode_alternates": [],
+ "name": "ophiuchus",
+ "shortname": ":ophiuchus:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["ophiuchus", "serpent", "snake", "astrology", "greek", "constellation", "stars", "zodiac", "purple-square", "sign", "horoscope"],
+ "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": [],
+ "name": "orange book",
+ "shortname": ":orange_book:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["knowledge", "library", "read"],
+ "moji": "📙"
+ },
+ "outbox_tray": {
+ "unicode": "1F4E4",
+ "unicode_alternates": [],
+ "name": "outbox tray",
+ "shortname": ":outbox_tray:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["email", "inbox"],
+ "moji": "📤"
+ },
+ "ox": {
+ "unicode": "1F402",
+ "unicode_alternates": [],
+ "name": "ox",
+ "shortname": ":ox:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "beef", "cow"],
+ "moji": "🐂"
+ },
+ "package": {
+ "unicode": "1F4E6",
+ "unicode_alternates": [],
+ "name": "package",
+ "shortname": ":package:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["gift", "mail"],
+ "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": [],
+ "name": "page facing up",
+ "shortname": ":page_facing_up:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["documents"],
+ "moji": "📄"
+ },
+ "page_with_curl": {
+ "unicode": "1F4C3",
+ "unicode_alternates": [],
+ "name": "page with curl",
+ "shortname": ":page_with_curl:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["documents"],
+ "moji": "📃"
+ },
+ "pager": {
+ "unicode": "1F4DF",
+ "unicode_alternates": [],
+ "name": "pager",
+ "shortname": ":pager:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["bbcall", "oldschool"],
+ "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",
+ "aliases": [":lower_left_paintbrush:"],
+ "aliases_ascii": [],
+ "keywords": ["brush", "art", "painting"]
+ },
+ "palm_tree": {
+ "unicode": "1F334",
+ "unicode_alternates": [],
+ "name": "palm tree",
+ "shortname": ":palm_tree:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["nature", "plant", "vegetable", "palm", "tree", "coconuts", "fronds", "warm", "tropical"],
+ "moji": "🌴"
+ },
+ "panda_face": {
+ "unicode": "1F43C",
+ "unicode_alternates": [],
+ "name": "panda face",
+ "shortname": ":panda_face:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "nature", "panda", "bear", "face", "cub", "cute", "endearment", "friendship", "love", "bamboo", "china", "black", "white"],
+ "moji": "🐼"
+ },
+ "paperclip": {
+ "unicode": "1F4CE",
+ "unicode_alternates": [],
+ "name": "paperclip",
+ "shortname": ":paperclip:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["documents", "stationery"],
+ "moji": "📎"
+ },
+ "paperclips": {
+ "unicode": "1F587",
+ "unicode_alternates": [],
+ "name": "linked paperclips",
+ "shortname": ":paperclips:",
+ "category": "objects_symbols",
+ "aliases": [":linked_paperclips:"],
+ "aliases_ascii": [],
+ "keywords": ["documents", "stationery"]
+ },
+ "park": {
+ "unicode": "1F3DE",
+ "unicode_alternates": [],
+ "name": "national park",
+ "shortname": ":park:",
+ "category": "travel_places",
+ "aliases": [":national_park:"],
+ "aliases_ascii": [],
+ "keywords": ["woods", "nature", "wildlife", "forest", "wilderness", "national"]
+ },
+ "parking": {
+ "unicode": "1F17F",
+ "unicode_alternates": ["1F17F-FE0F"],
+ "name": "negative squared latin capital letter p",
+ "shortname": ":parking:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["alphabet", "blue-square", "cars", "letter"],
+ "moji": "🅿"
+ },
+ "part_alternation_mark": {
+ "unicode": "303D",
+ "unicode_alternates": ["303D-FE0F"],
+ "name": "part alternation mark",
+ "shortname": ":part_alternation_mark:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["graph", "sing", "song", "vocal", "music", "karaoke", "cue", "letter", "m", "japanese"],
+ "moji": "〽"
+ },
+ "partly_sunny": {
+ "unicode": "26C5",
+ "unicode_alternates": ["26C5-FE0F"],
+ "name": "sun behind cloud",
+ "shortname": ":partly_sunny:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["cloud", "morning", "nature", "weather"],
+ "moji": "⛅"
+ },
+ "passport_control": {
+ "unicode": "1F6C2",
+ "unicode_alternates": [],
+ "name": "passport control",
+ "shortname": ":passport_control:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blue-square", "custom", "passport", "official", "travel", "control", "foreign", "identification"],
+ "moji": "🛂"
+ },
+ "peach": {
+ "unicode": "1F351",
+ "unicode_alternates": [],
+ "name": "peach",
+ "shortname": ":peach:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["food", "fruit", "nature", "peach", "fruit", "juicy", "pit"],
+ "moji": "🍑"
+ },
+ "pear": {
+ "unicode": "1F350",
+ "unicode_alternates": [],
+ "name": "pear",
+ "shortname": ":pear:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["fruit", "nature", "pear", "fruit", "shape"],
+ "moji": "🍐"
+ },
+ "pen_ballpoint": {
+ "unicode": "1F58A",
+ "unicode_alternates": [],
+ "name": "lower left ballpoint pen",
+ "shortname": ":pen_ballpoint:",
+ "category": "objects_symbols",
+ "aliases": [":lower_left_ballpoint_pen:"],
+ "aliases_ascii": [],
+ "keywords": ["write", "bic", "ink"]
+ },
+ "pen_fountain": {
+ "unicode": "1F58B",
+ "unicode_alternates": [],
+ "name": "lower left fountain pen",
+ "shortname": ":pen_fountain:",
+ "category": "objects_symbols",
+ "aliases": [":lower_left_fountain_pen:"],
+ "aliases_ascii": [],
+ "keywords": ["write", "calligraphy", "ink"]
+ },
+ "pencil": {
+ "unicode": "1F4DD",
+ "unicode_alternates": [],
+ "name": "memo",
+ "shortname": ":pencil:",
+ "category": "objects",
+ "aliases": [":memo:"],
+ "aliases_ascii": [],
+ "keywords": ["documents", "paper", "station", "write"],
+ "moji": "📝"
+ },
+ "pencil2": {
+ "unicode": "270F",
+ "unicode_alternates": ["270F-FE0F"],
+ "name": "pencil",
+ "shortname": ":pencil2:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["paper", "stationery", "write"],
+ "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": [],
+ "name": "penguin",
+ "shortname": ":penguin:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "nature"],
+ "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",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["face", "okay", "sad", "pensive", "thoughtful", "think", "reflective", "wistful", "meditate", "serious"],
+ "moji": "😔"
+ },
+ "performing_arts": {
+ "unicode": "1F3AD",
+ "unicode_alternates": [],
+ "name": "performing arts",
+ "shortname": ":performing_arts:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["acting", "drama", "theater", "performing", "arts", "performance", "entertainment", "acting", "story", "mask", "masks"],
+ "moji": "🎭"
+ },
+ "persevere": {
+ "unicode": "1F623",
+ "unicode_alternates": [],
+ "name": "persevering face",
+ "shortname": ":persevere:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [">.<"],
+ "keywords": ["endure", "persevere", "face", "no", "sick", "upset"],
+ "moji": "😣"
+ },
+ "person_frowning": {
+ "unicode": "1F64D",
+ "unicode_alternates": [],
+ "name": "person frowning",
+ "shortname": ":person_frowning:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["female", "girl", "woman", "dejected", "rejected", "sad", "frown"],
+ "moji": "🙍"
+ },
+ "person_with_blond_hair": {
+ "unicode": "1F471",
+ "unicode_alternates": [],
+ "name": "person with blond hair",
+ "shortname": ":person_with_blond_hair:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["male", "man", "blonde", "young", "western", "westerner", "occidental"],
+ "moji": "👱"
+ },
+ "person_with_pouting_face": {
+ "unicode": "1F64E",
+ "unicode_alternates": [],
+ "name": "person with pouting face",
+ "shortname": ":person_with_pouting_face:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["female", "girl", "woman", "pout", "sexy", "cute", "annoyed"],
+ "moji": "🙎"
+ },
+ "pig": {
+ "unicode": "1F437",
+ "unicode_alternates": [],
+ "name": "pig face",
+ "shortname": ":pig:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "oink"],
+ "moji": "🐷"
+ },
+ "pig2": {
+ "unicode": "1F416",
+ "unicode_alternates": [],
+ "name": "pig",
+ "shortname": ":pig2:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "nature", "pig", "piggy", "pork", "ham", "hog", "bacon", "oink", "slop", "livestock", "greed", "greedy"],
+ "moji": "🐖"
+ },
+ "pig_nose": {
+ "unicode": "1F43D",
+ "unicode_alternates": [],
+ "name": "pig nose",
+ "shortname": ":pig_nose:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "oink", "pig", "nose", "snout", "food", "eat", "cute", "oink", "pink", "smell", "truffle"],
+ "moji": "🐽"
+ },
+ "pill": {
+ "unicode": "1F48A",
+ "unicode_alternates": [],
+ "name": "pill",
+ "shortname": ":pill:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["health", "medicine"],
+ "moji": "💊"
+ },
+ "pineapple": {
+ "unicode": "1F34D",
+ "unicode_alternates": [],
+ "name": "pineapple",
+ "shortname": ":pineapple:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["food", "fruit", "nature", "pineapple", "pina", "tropical", "flower"],
+ "moji": "🍍"
+ },
+ "piracy": {
+ "unicode": "1F572",
+ "unicode_alternates": [],
+ "name": "no piracy",
+ "shortname": ":piracy:",
+ "category": "objects_symbols",
+ "aliases": [":no_piracy:"],
+ "aliases_ascii": [],
+ "keywords": ["theft", "rule"]
+ },
+ "pisces": {
+ "unicode": "2653",
+ "unicode_alternates": ["2653-FE0F"],
+ "name": "pisces",
+ "shortname": ":pisces:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["pisces", "fish", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "purple-square", "sign", "zodiac", "horoscope"],
+ "moji": "♓"
+ },
+ "pizza": {
+ "unicode": "1F355",
+ "unicode_alternates": [],
+ "name": "slice of pizza",
+ "shortname": ":pizza:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["food", "party", "pizza", "pie", "new york", "italian", "italy", "slice", "peperoni"],
+ "moji": "🍕"
+ },
+ "point_down": {
+ "unicode": "1F447",
+ "unicode_alternates": [],
+ "name": "white down pointing backhand index",
+ "shortname": ":point_down:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["direction", "fingers", "hand"],
+ "moji": "👇"
+ },
+ "point_left": {
+ "unicode": "1F448",
+ "unicode_alternates": [],
+ "name": "white left pointing backhand index",
+ "shortname": ":point_left:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["direction", "fingers", "hand"],
+ "moji": "👈"
+ },
+ "point_right": {
+ "unicode": "1F449",
+ "unicode_alternates": [],
+ "name": "white right pointing backhand index",
+ "shortname": ":point_right:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["direction", "fingers", "hand"],
+ "moji": "👉"
+ },
+ "point_up": {
+ "unicode": "261D",
+ "unicode_alternates": ["261D-FE0F"],
+ "name": "white up pointing index",
+ "shortname": ":point_up:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["direction", "fingers", "hand"],
+ "moji": "☝"
+ },
+ "point_up_2": {
+ "unicode": "1F446",
+ "unicode_alternates": [],
+ "name": "white up pointing backhand index",
+ "shortname": ":point_up_2:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["direction", "fingers", "hand"],
+ "moji": "👆"
+ },
+ "police_car": {
+ "unicode": "1F693",
+ "unicode_alternates": [],
+ "name": "police car",
+ "shortname": ":police_car:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["cars", "enforcement", "law", "transportation", "vehicle", "police", "car", "emergency", "ticket", "citation", "crime", "help", "officer"],
+ "moji": "🚓"
+ },
+ "poodle": {
+ "unicode": "1F429",
+ "unicode_alternates": [],
+ "name": "poodle",
+ "shortname": ":poodle:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["101", "animal", "dog", "nature", "poodle", "dog", "clip", "showy", "sophisticated", "vain"],
+ "moji": "🐩"
+ },
+ "poop": {
+ "unicode": "1F4A9",
+ "unicode_alternates": [],
+ "name": "pile of poo",
+ "shortname": ":poop:",
+ "category": "emoticons",
+ "aliases": [":shit:", ":hankey:", ":poo:"],
+ "aliases_ascii": [],
+ "keywords": ["poop", "shit", "shitface", "turd", "poo"],
+ "moji": "💩"
+ },
+ "post_office": {
+ "unicode": "1F3E3",
+ "unicode_alternates": [],
+ "name": "japanese post office",
+ "shortname": ":post_office:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["building", "communication", "email"],
+ "moji": "🏣"
+ },
+ "postal_horn": {
+ "unicode": "1F4EF",
+ "unicode_alternates": [],
+ "name": "postal horn",
+ "shortname": ":postal_horn:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["instrument", "music"],
+ "moji": "📯"
+ },
+ "postbox": {
+ "unicode": "1F4EE",
+ "unicode_alternates": [],
+ "name": "postbox",
+ "shortname": ":postbox:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["email", "envelope", "letter"],
+ "moji": "📮"
+ },
+ "potable_water": {
+ "unicode": "1F6B0",
+ "unicode_alternates": [],
+ "name": "potable water symbol",
+ "shortname": ":potable_water:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blue-square", "cleaning", "faucet", "liquid", "restroom", "potable", "water", "drinkable", "pure", "clear", "clean", "aqua", "h20"],
+ "moji": "🚰"
+ },
+ "pouch": {
+ "unicode": "1F45D",
+ "unicode_alternates": [],
+ "name": "pouch",
+ "shortname": ":pouch:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["accessories", "bag", "pouch", "bag", "cosmetic", "packing", "grandma", "makeup"],
+ "moji": "👝"
+ },
+ "poultry_leg": {
+ "unicode": "1F357",
+ "unicode_alternates": [],
+ "name": "poultry leg",
+ "shortname": ":poultry_leg:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["food", "meat", "poultry", "leg", "chicken", "fried"],
+ "moji": "🍗"
+ },
+ "pound": {
+ "unicode": "1F4B7",
+ "unicode_alternates": [],
+ "name": "banknote with pound sign",
+ "shortname": ":pound:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["bills", "british", "currency", "england", "money", "sterling", "uk", "pound", "britain", "british", "banknote", "money", "currency", "paper", "cash", "bills"],
+ "moji": "💷"
+ },
+ "pouting_cat": {
+ "unicode": "1F63E",
+ "unicode_alternates": [],
+ "name": "pouting cat face",
+ "shortname": ":pouting_cat:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "cats", "pout", "annoyed", "miffed", "glower", "frown"],
+ "moji": "😾"
+ },
+ "pray": {
+ "unicode": "1F64F",
+ "unicode_alternates": [],
+ "name": "person with folded hands",
+ "shortname": ":pray:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["highfive", "hope", "namaste", "please", "wish", "pray", "high five", "hands", "sorrow", "regret", "sorry"],
+ "moji": "🙏"
+ },
+ "princess": {
+ "unicode": "1F478",
+ "unicode_alternates": [],
+ "name": "princess",
+ "shortname": ":princess:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blond", "crown", "female", "girl", "woman", "princess", "royal", "royalty", "king", "queen", "daughter", "disney", "high-maintenance"],
+ "moji": "👸"
+ },
+ "printer": {
+ "unicode": "1F5A8",
+ "unicode_alternates": [],
+ "name": "printer",
+ "shortname": ":printer:",
+ "category": "objects_symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["hardcopy", "paper", "inkjet", "laser"]
+ },
+ "prohibited": {
+ "unicode": "1F6C7",
+ "unicode_alternates": [],
+ "name": "prohibited sign",
+ "shortname": ":prohibited:",
+ "category": "objects_symbols",
+ "aliases": [":prohibited_sign:"],
+ "aliases_ascii": [],
+ "keywords": ["no", "not", "denied", "disallow", "forbid", "limit", "stop"]
+ },
+ "projector": {
+ "unicode": "1F4FD",
+ "unicode_alternates": [],
+ "name": "film projector",
+ "shortname": ":projector:",
+ "category": "objects_symbols",
+ "aliases": [":film_projector:"],
+ "aliases_ascii": [],
+ "keywords": ["movie", "video", "motion", "picture", "8mm", "16mm"]
+ },
+ "punch": {
+ "unicode": "1F44A",
+ "unicode_alternates": [],
+ "name": "fisted hand sign",
+ "shortname": ":punch:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["fist", "hand"],
+ "moji": "👊"
+ },
+ "purple_heart": {
+ "unicode": "1F49C",
+ "unicode_alternates": [],
+ "name": "purple heart",
+ "shortname": ":purple_heart:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["affection", "like", "love", "valentines", "purple", "violet", "heart", "love", "sensitive", "understanding", "compassionate", "compassion", "duty", "honor", "royalty", "veteran", "sacrifice"],
+ "moji": "💜"
+ },
+ "purse": {
+ "unicode": "1F45B",
+ "unicode_alternates": [],
+ "name": "purse",
+ "shortname": ":purse:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["accessories", "fashion", "money", "purse", "clutch", "bag", "handbag", "coin bag", "accessory", "money", "ladies", "shopping"],
+ "moji": "👛"
+ },
+ "pushpin": {
+ "unicode": "1F4CC",
+ "unicode_alternates": [],
+ "name": "pushpin",
+ "shortname": ":pushpin:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["stationery"],
+ "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",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blue-square", "litter", "waste", "trash", "garbage", "receptacle", "can"],
+ "moji": "🚮"
+ },
+ "question": {
+ "unicode": "2753",
+ "unicode_alternates": [],
+ "name": "black question mark ornament",
+ "shortname": ":question:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["confused", "doubt"],
+ "moji": "❓"
+ },
+ "rabbit": {
+ "unicode": "1F430",
+ "unicode_alternates": [],
+ "name": "rabbit face",
+ "shortname": ":rabbit:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "nature"],
+ "moji": "🐰"
+ },
+ "rabbit2": {
+ "unicode": "1F407",
+ "unicode_alternates": [],
+ "name": "rabbit",
+ "shortname": ":rabbit2:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "nature", "rabbit", "bunny", "easter", "reproduction", "prolific"],
+ "moji": "🐇"
+ },
+ "race_car": {
+ "unicode": "1F3CE",
+ "unicode_alternates": [],
+ "name": "racing car",
+ "shortname": ":race_car:",
+ "category": "activity",
+ "aliases": [":racing_car:"],
+ "aliases_ascii": [],
+ "keywords": ["formula 1", "race", "stock", "nascar", "speed", "drive"]
+ },
+ "racehorse": {
+ "unicode": "1F40E",
+ "unicode_alternates": [],
+ "name": "horse",
+ "shortname": ":racehorse:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "gamble", "horse", "powerful", "draft", "calvary", "cowboy", "cowgirl", "mounted", "race", "ride", "gallop", "trot", "colt", "filly", "mare", "stallion", "gelding", "yearling", "thoroughbred", "pony"],
+ "moji": "🐎"
+ },
+ "radio": {
+ "unicode": "1F4FB",
+ "unicode_alternates": [],
+ "name": "radio",
+ "shortname": ":radio:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["communication", "music", "podcast", "program"],
+ "moji": "📻"
+ },
+ "radio_button": {
+ "unicode": "1F518",
+ "unicode_alternates": [],
+ "name": "radio button",
+ "shortname": ":radio_button:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["input"],
+ "moji": "🔘"
+ },
+ "rage": {
+ "unicode": "1F621",
+ "unicode_alternates": [],
+ "name": "pouting face",
+ "shortname": ":rage:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["angry", "despise", "hate", "mad", "pout", "anger", "rage", "irate"],
+ "moji": "😡"
+ },
+ "railway_car": {
+ "unicode": "1F683",
+ "unicode_alternates": [],
+ "name": "railway car",
+ "shortname": ":railway_car:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["transportation", "vehicle", "railway", "rail", "car", "coach", "train"],
+ "moji": "🚃"
+ },
+ "railway_track": {
+ "unicode": "1F6E4",
+ "unicode_alternates": [],
+ "name": "railway track",
+ "shortname": ":railway_track:",
+ "category": "travel_places",
+ "aliases": [":railroad_track:"],
+ "aliases_ascii": [],
+ "keywords": ["train", "trolley", "subway", "locomotive", "transit"]
+ },
+ "rainbow": {
+ "unicode": "1F308",
+ "unicode_alternates": [],
+ "name": "rainbow",
+ "shortname": ":rainbow:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["happy", "nature", "photo", "sky", "unicorn", "rainbow", "color", "pride", "diversity", "spectrum", "refract", "leprechaun", "gold"],
+ "moji": "🌈"
+ },
+ "raised_hand": {
+ "unicode": "270B",
+ "unicode_alternates": [],
+ "name": "raised hand",
+ "shortname": ":raised_hand:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["female", "girl", "woman"],
+ "moji": "✋"
+ },
+ "raised_hands": {
+ "unicode": "1F64C",
+ "unicode_alternates": [],
+ "name": "person raising both hands in celebration",
+ "shortname": ":raised_hands:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["gesture", "hooray", "winning", "woot", "yay", "banzai"],
+ "moji": "🙌"
+ },
+ "raising_hand": {
+ "unicode": "1F64B",
+ "unicode_alternates": [],
+ "name": "happy person raising one hand",
+ "shortname": ":raising_hand:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["female", "girl", "woman", "hand", "raise", "notice", "attention", "answer"],
+ "moji": "🙋"
+ },
+ "ram": {
+ "unicode": "1F40F",
+ "unicode_alternates": [],
+ "name": "ram",
+ "shortname": ":ram:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "nature", "sheep", "ram", "sheep", "male", "horn", "horns"],
+ "moji": "🐏"
+ },
+ "ramen": {
+ "unicode": "1F35C",
+ "unicode_alternates": [],
+ "name": "steaming bowl",
+ "shortname": ":ramen:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["chipsticks", "food", "japanese", "noodle", "ramen", "noodles", "bowl", "steaming", "soup"],
+ "moji": "🍜"
+ },
+ "rat": {
+ "unicode": "1F400",
+ "unicode_alternates": [],
+ "name": "rat",
+ "shortname": ":rat:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "mouse", "rat", "rodent", "crooked", "snitch"],
+ "moji": "🐀"
+ },
+ "recycle": {
+ "unicode": "267B",
+ "unicode_alternates": ["267B-FE0F"],
+ "name": "black universal recycling symbol",
+ "shortname": ":recycle:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["arrow", "environment", "garbage", "trash"],
+ "moji": "♻"
+ },
+ "red_car": {
+ "unicode": "1F697",
+ "unicode_alternates": [],
+ "name": "automobile",
+ "shortname": ":red_car:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["transportation", "vehicle"],
+ "moji": "🚗"
+ },
+ "red_circle": {
+ "unicode": "1F534",
+ "unicode_alternates": [],
+ "name": "large red circle",
+ "shortname": ":red_circle:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["shape"],
+ "moji": "🔴"
+ },
+ "registered": {
+ "moji": "®",
+ "unicode": "00AE",
+ "unicode_alternates": [],
+ "name": "registered sign",
+ "shortname": ":registered:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["alphabet", "circle"]
+ },
+ "relaxed": {
+ "unicode": "263A",
+ "unicode_alternates": ["263A-FE0F"],
+ "name": "white smiling face",
+ "shortname": ":relaxed:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blush", "face", "happiness", "massage", "smile"],
+ "moji": "☺"
+ },
+ "relieved": {
+ "unicode": "1F60C",
+ "unicode_alternates": [],
+ "name": "relieved face",
+ "shortname": ":relieved:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["face", "happiness", "massage", "phew", "relaxed", "relieved", "satisfied", "phew", "relief"],
+ "moji": "😌"
+ },
+ "reminder_ribbon": {
+ "unicode": "1F397",
+ "unicode_alternates": [],
+ "name": "reminder ribbon",
+ "shortname": ":reminder_ribbon:",
+ "category": "celebration",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["awareness"]
+ },
+ "repeat": {
+ "unicode": "1F501",
+ "unicode_alternates": [],
+ "name": "clockwise rightwards and leftwards open circle arr",
+ "shortname": ":repeat:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["loop", "record"],
+ "moji": "🔁"
+ },
+ "repeat_one": {
+ "unicode": "1F502",
+ "unicode_alternates": [],
+ "name": "clockwise rightwards and leftwards open circle arr",
+ "shortname": ":repeat_one:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blue-square", "loop"],
+ "moji": "🔂"
+ },
+ "restroom": {
+ "unicode": "1F6BB",
+ "unicode_alternates": [],
+ "name": "restroom",
+ "shortname": ":restroom:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blue-square", "woman", "man", "unisex", "bathroom", "restroom", "sign", "shared", "toilet"],
+ "moji": "🚻"
+ },
+ "revolving_hearts": {
+ "unicode": "1F49E",
+ "unicode_alternates": [],
+ "name": "revolving hearts",
+ "shortname": ":revolving_hearts:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["affection", "like", "love", "valentines", "heart", "hearts", "revolving", "moving", "circle", "multiple", "lovers"],
+ "moji": "💞"
+ },
+ "rewind": {
+ "unicode": "23EA",
+ "unicode_alternates": [],
+ "name": "black left-pointing double triangle",
+ "shortname": ":rewind:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blue-square", "play"],
+ "moji": "⏪"
+ },
+ "ribbon": {
+ "unicode": "1F380",
+ "unicode_alternates": [],
+ "name": "ribbon",
+ "shortname": ":ribbon:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["bowtie", "decoration", "girl", "pink", "ribbon", "lace", "wrap", "decorate"],
+ "moji": "🎀"
+ },
+ "rice": {
+ "unicode": "1F35A",
+ "unicode_alternates": [],
+ "name": "cooked rice",
+ "shortname": ":rice:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["food", "rice", "white", "grain", "food", "bowl"],
+ "moji": "🍚"
+ },
+ "rice_ball": {
+ "unicode": "1F359",
+ "unicode_alternates": [],
+ "name": "rice ball",
+ "shortname": ":rice_ball:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["food", "japanese", "rice", "ball", "white", "nori", "seaweed", "japanese"],
+ "moji": "🍙"
+ },
+ "rice_cracker": {
+ "unicode": "1F358",
+ "unicode_alternates": [],
+ "name": "rice cracker",
+ "shortname": ":rice_cracker:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["food", "japanese", "rice", "cracker", "seaweed", "food", "japanese"],
+ "moji": "🍘"
+ },
+ "rice_scene": {
+ "unicode": "1F391",
+ "unicode_alternates": [],
+ "name": "moon viewing ceremony",
+ "shortname": ":rice_scene:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["photo", "moon", "viewing", "observing", "otsukimi", "tsukimi", "rice", "scene", "festival", "autumn"],
+ "moji": "🎑"
+ },
+ "right_speaker": {
+ "unicode": "1F568",
+ "unicode_alternates": [],
+ "name": "right speaker",
+ "shortname": ":right_speaker:",
+ "category": "objects_symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["sound", "listen", "hear", "noise", "volume"]
+ },
+ "right_speaker_one": {
+ "unicode": "1F569",
+ "unicode_alternates": [],
+ "name": "right speaker with one sound wave",
+ "shortname": ":right_speaker_one:",
+ "category": "objects_symbols",
+ "aliases": [":right_speaker_with_one_sound_wave:"],
+ "aliases_ascii": [],
+ "keywords": ["low", "volume"]
+ },
+ "right_speaker_three": {
+ "unicode": "1F56A",
+ "unicode_alternates": [],
+ "name": "right speaker with three sound waves",
+ "shortname": ":right_speaker_three:",
+ "category": "objects_symbols",
+ "aliases": [":right_speaker_with_three_sound_waves:"],
+ "aliases_ascii": [],
+ "keywords": ["loud", "high", "volume"]
+ },
+ "ring": {
+ "unicode": "1F48D",
+ "unicode_alternates": [],
+ "name": "ring",
+ "shortname": ":ring:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["marriage", "propose", "valentines", "wedding"],
+ "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"]
+ },
+ "rocket": {
+ "unicode": "1F680",
+ "unicode_alternates": [],
+ "name": "rocket",
+ "shortname": ":rocket:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["launch", "ship", "staffmode", "rocket", "space", "spacecraft", "astronaut", "cosmonaut"],
+ "moji": "🚀"
+ },
+ "roller_coaster": {
+ "unicode": "1F3A2",
+ "unicode_alternates": [],
+ "name": "roller coaster",
+ "shortname": ":roller_coaster:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["carnival", "fun", "photo", "play", "playground", "roller", "coaster", "amusement", "park", "fair", "ride", "entertainment"],
+ "moji": "🎢"
+ },
+ "rooster": {
+ "unicode": "1F413",
+ "unicode_alternates": [],
+ "name": "rooster",
+ "shortname": ":rooster:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "chicken", "nature", "rooster", "cockerel", "cock", "male", "cock-a-doodle-doo", "crowing"],
+ "moji": "🐓"
+ },
+ "rose": {
+ "unicode": "1F339",
+ "unicode_alternates": [],
+ "name": "rose",
+ "shortname": ":rose:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["flowers", "love", "valentines", "rose", "fragrant", "flower", "thorns", "love", "petals", "romance"],
+ "moji": "🌹"
+ },
+ "rosette": {
+ "unicode": "1F3F5",
+ "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",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["flower"]
+ },
+ "rotating_light": {
+ "unicode": "1F6A8",
+ "unicode_alternates": [],
+ "name": "police cars revolving light",
+ "shortname": ":rotating_light:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["911", "ambulance", "emergency", "police", "light", "police", "emergency"],
+ "moji": "🚨"
+ },
+ "round_pushpin": {
+ "unicode": "1F4CD",
+ "unicode_alternates": [],
+ "name": "round pushpin",
+ "shortname": ":round_pushpin:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["stationery"],
+ "moji": "📍"
+ },
+ "rowboat": {
+ "unicode": "1F6A3",
+ "unicode_alternates": [],
+ "name": "rowboat",
+ "shortname": ":rowboat:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["hobby", "ship", "sports", "water", "boat", "row", "oar", "paddle"],
+ "moji": "🚣"
+ },
+ "rugby_football": {
+ "unicode": "1F3C9",
+ "unicode_alternates": [],
+ "name": "rugby football",
+ "shortname": ":rugby_football:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["sports", "rugby", "football", "ball", "sport", "team", "england"],
+ "moji": "🏉"
+ },
+ "runner": {
+ "unicode": "1F3C3",
+ "unicode_alternates": [],
+ "name": "runner",
+ "shortname": ":runner:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["exercise", "man", "walking", "run", "runner", "jog", "exercise", "sprint", "race", "dash"],
+ "moji": "🏃"
+ },
+ "running_shirt_with_sash": {
+ "unicode": "1F3BD",
+ "unicode_alternates": [],
+ "name": "running shirt with sash",
+ "shortname": ":running_shirt_with_sash:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["pageant", "play", "running", "run", "shirt", "cloths", "compete", "sports"],
+ "moji": "🎽"
+ },
+ "sagittarius": {
+ "unicode": "2650",
+ "unicode_alternates": ["2650-FE0F"],
+ "name": "sagittarius",
+ "shortname": ":sagittarius:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["sagittarius", "centaur", "archer", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "sign", "zodiac", "horoscope"],
+ "moji": "♐"
+ },
+ "sailboat": {
+ "unicode": "26F5",
+ "unicode_alternates": ["26F5-FE0F"],
+ "name": "sailboat",
+ "shortname": ":sailboat:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["ship", "transportation"],
+ "moji": "⛵"
+ },
+ "sake": {
+ "unicode": "1F376",
+ "unicode_alternates": [],
+ "name": "sake bottle and cup",
+ "shortname": ":sake:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["beverage", "drink", "drunk", "wine", "sake", "wine", "rice", "ferment", "alcohol", "japanese", "drink"],
+ "moji": "🍶"
+ },
+ "sandal": {
+ "unicode": "1F461",
+ "unicode_alternates": [],
+ "name": "womans sandal",
+ "shortname": ":sandal:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["fashion", "shoes"],
+ "moji": "👡"
+ },
+ "santa": {
+ "unicode": "1F385",
+ "unicode_alternates": [],
+ "name": "father christmas",
+ "shortname": ":santa:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["christmas", "father christmas", "festival", "male", "man", "xmas", "santa", "saint nick", "jolly", "ho ho ho", "north pole", "presents", "gifts", "naughty", "nice", "sleigh", "father", "christmas", "holiday"],
+ "moji": "🎅"
+ },
+ "satellite": {
+ "unicode": "1F4E1",
+ "unicode_alternates": [],
+ "name": "satellite antenna",
+ "shortname": ":satellite:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["communication"],
+ "moji": "📡"
+ },
+ "satellite_orbital": {
+ "unicode": "1F6F0",
+ "unicode_alternates": [],
+ "name": "satellite",
+ "shortname": ":satellite_orbital:",
+ "category": "objects_symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["communication", "orbital", "space"]
+ },
+ "saxophone": {
+ "unicode": "1F3B7",
+ "unicode_alternates": [],
+ "name": "saxophone",
+ "shortname": ":saxophone:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["instrument", "music", "saxophone", "sax", "music", "instrument", "woodwind"],
+ "moji": "🎷"
+ },
+ "school": {
+ "unicode": "1F3EB",
+ "unicode_alternates": [],
+ "name": "school",
+ "shortname": ":school:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["building", "school", "university", "elementary", "middle", "high", "college", "teach", "education"],
+ "moji": "🏫"
+ },
+ "school_satchel": {
+ "unicode": "1F392",
+ "unicode_alternates": [],
+ "name": "school satchel",
+ "shortname": ":school_satchel:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["bag", "education", "student", "school", "satchel", "backpack", "bag", "packing", "pack", "hike", "education", "adventure", "travel", "sightsee"],
+ "moji": "🎒"
+ },
+ "scissors": {
+ "unicode": "2702",
+ "unicode_alternates": ["2702-FE0F"],
+ "name": "black scissors",
+ "shortname": ":scissors:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["cut", "stationery"],
+ "moji": "✂"
+ },
+ "scorpius": {
+ "unicode": "264F",
+ "unicode_alternates": ["264F-FE0F"],
+ "name": "scorpius",
+ "shortname": ":scorpius:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["scorpius", "scorpion", "scorpio", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "sign", "zodiac", "horoscope"],
+ "moji": "♏"
+ },
+ "scream": {
+ "unicode": "1F631",
+ "unicode_alternates": [],
+ "name": "face screaming in fear",
+ "shortname": ":scream:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["face", "munch", "scream", "painting", "artist", "alien"],
+ "moji": "😱"
+ },
+ "scream_cat": {
+ "unicode": "1F640",
+ "unicode_alternates": [],
+ "name": "weary cat face",
+ "shortname": ":scream_cat:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "cats", "munch", "weary", "sleepy", "tired", "tiredness", "study", "finals", "school", "exhausted", "scream", "painting", "artist"],
+ "moji": "🙀"
+ },
+ "scroll": {
+ "unicode": "1F4DC",
+ "unicode_alternates": [],
+ "name": "scroll",
+ "shortname": ":scroll:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["documents"],
+ "moji": "📜"
+ },
+ "seat": {
+ "unicode": "1F4BA",
+ "unicode_alternates": [],
+ "name": "seat",
+ "shortname": ":seat:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["sit"],
+ "moji": "💺"
+ },
+ "secret": {
+ "unicode": "3299",
+ "unicode_alternates": ["3299-FE0F"],
+ "name": "circled ideograph secret",
+ "shortname": ":secret:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["privacy"],
+ "moji": "㊙"
+ },
+ "see_no_evil": {
+ "unicode": "1F648",
+ "unicode_alternates": [],
+ "name": "see-no-evil monkey",
+ "shortname": ":see_no_evil:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "monkey", "nature", "monkey", "see", "eyes", "vision", "sight", "mizaru"],
+ "moji": "🙈"
+ },
+ "seedling": {
+ "unicode": "1F331",
+ "unicode_alternates": [],
+ "name": "seedling",
+ "shortname": ":seedling:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["grass", "lawn", "nature", "plant", "seedling", "plant", "new", "start", "grow"],
+ "moji": "🌱"
+ },
+ "seven": {
+ "moji": "7️⃣",
+ "unicode": "0037-20E3",
+ "unicode_alternates": ["0037-FE0F-20E3"],
+ "name": "digit seven",
+ "shortname": ":seven:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["7", "blue-square", "numbers", "prime"]
+ },
+ "shaved_ice": {
+ "unicode": "1F367",
+ "unicode_alternates": [],
+ "name": "shaved ice",
+ "shortname": ":shaved_ice:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["desert", "hot", "shaved", "ice", "dessert", "treat", "syrup", "flavoring"],
+ "moji": "🍧"
+ },
+ "sheep": {
+ "unicode": "1F411",
+ "unicode_alternates": [],
+ "name": "sheep",
+ "shortname": ":sheep:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "nature", "sheep", "wool", "flock", "follower", "ewe", "female", "lamb"],
+ "moji": "🐑"
+ },
+ "shell": {
+ "unicode": "1F41A",
+ "unicode_alternates": [],
+ "name": "spiral shell",
+ "shortname": ":shell:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["beach", "nature", "sea", "shell", "spiral", "beach", "sand", "crab", "nautilus"],
+ "moji": "🐚"
+ },
+ "shield": {
+ "unicode": "1F6E1",
+ "unicode_alternates": [],
+ "name": "shield",
+ "shortname": ":shield:",
+ "category": "objects_symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["interstate", "route", "sign", "highway", "interstate"]
+ },
+ "ship": {
+ "unicode": "1F6A2",
+ "unicode_alternates": [],
+ "name": "ship",
+ "shortname": ":ship:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["titanic", "transportation", "ferry", "ship", "boat"],
+ "moji": "🚢"
+ },
+ "shirt": {
+ "unicode": "1F455",
+ "unicode_alternates": [],
+ "name": "t-shirt",
+ "shortname": ":shirt:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["cloth", "fashion"],
+ "moji": "👕"
+ },
+ "shopping_bags": {
+ "unicode": "1F6CD",
+ "unicode_alternates": [],
+ "name": "shopping bags",
+ "shortname": ":shopping_bags:",
+ "category": "travel_places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["purchase", "mall", "buy", "store", "shop"]
+ },
+ "shower": {
+ "unicode": "1F6BF",
+ "unicode_alternates": [],
+ "name": "shower",
+ "shortname": ":shower:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["bath", "clean", "wash", "bathroom", "shower", "soap", "water", "clean", "shampoo", "lather"],
+ "moji": "🚿"
+ },
+ "signal_strength": {
+ "unicode": "1F4F6",
+ "unicode_alternates": [],
+ "name": "antenna with bars",
+ "shortname": ":signal_strength:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blue-square"],
+ "moji": "📶"
+ },
+ "six": {
+ "moji": "6️⃣",
+ "unicode": "0036-20E3",
+ "unicode_alternates": ["0036-FE0F-20E3"],
+ "name": "digit six",
+ "shortname": ":six:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["6", "blue-square", "numbers"]
+ },
+ "six_pointed_star": {
+ "unicode": "1F52F",
+ "unicode_alternates": [],
+ "name": "six pointed star with middle dot",
+ "shortname": ":six_pointed_star:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["purple-square"],
+ "moji": "🔯"
+ },
+ "ski": {
+ "unicode": "1F3BF",
+ "unicode_alternates": [],
+ "name": "ski and ski boot",
+ "shortname": ":ski:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["cold", "sports", "winter", "ski", "downhill", "cross-country", "poles", "snow", "winter", "mountain", "alpine", "powder", "slalom", "freestyle"],
+ "moji": "🎿"
+ },
+ "skull": {
+ "unicode": "1F480",
+ "unicode_alternates": [],
+ "name": "skull",
+ "shortname": ":skull:",
+ "category": "emoticons",
+ "aliases": [":skeleton:"],
+ "aliases_ascii": [],
+ "keywords": ["dead", "skeleton", "dying"],
+ "moji": "💀"
+ },
+ "sleeping": {
+ "unicode": "1F634",
+ "unicode_alternates": [],
+ "name": "sleeping face",
+ "shortname": ":sleeping:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["face", "sleepy", "tired", "sleep", "sleepy", "sleeping", "snore"],
+ "moji": "😴"
+ },
+ "sleeping_accommodation": {
+ "unicode": "1F6CC",
+ "unicode_alternates": [],
+ "name": "sleeping accommodation",
+ "shortname": ":sleeping_accommodation:",
+ "category": "objects_symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["hotel", "motel", "rest"]
+ },
+ "sleepy": {
+ "unicode": "1F62A",
+ "unicode_alternates": [],
+ "name": "sleepy face",
+ "shortname": ":sleepy:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["face", "rest", "tired", "sleepy", "tired", "exhausted"],
+ "moji": "😪"
+ },
+ "slight_frown": {
+ "unicode": "1F641",
+ "unicode_alternates": [],
+ "name": "slightly frowning face",
+ "shortname": ":slight_frown:",
+ "category": "people",
+ "aliases": [":slightly_frowning_face:"],
+ "aliases_ascii": [],
+ "keywords": ["slight", "frown", "unhappy", "disappointed"]
+ },
+ "slight_smile": {
+ "unicode": "1F642",
+ "unicode_alternates": [],
+ "name": "slightly smiling face",
+ "shortname": ":slight_smile:",
+ "category": "people",
+ "aliases": [":slightly_smiling_face:"],
+ "aliases_ascii": [],
+ "keywords": ["slight", "smile", "happy"]
+ },
+ "slot_machine": {
+ "unicode": "1F3B0",
+ "unicode_alternates": [],
+ "name": "slot machine",
+ "shortname": ":slot_machine:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["bet", "gamble", "vegas", "slot", "machine", "gamble", "one-armed bandit", "slots", "luck"],
+ "moji": "🎰"
+ },
+ "small_blue_diamond": {
+ "unicode": "1F539",
+ "unicode_alternates": [],
+ "name": "small blue diamond",
+ "shortname": ":small_blue_diamond:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["shape"],
+ "moji": "🔹"
+ },
+ "small_orange_diamond": {
+ "unicode": "1F538",
+ "unicode_alternates": [],
+ "name": "small orange diamond",
+ "shortname": ":small_orange_diamond:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["shape"],
+ "moji": "🔸"
+ },
+ "small_red_triangle": {
+ "unicode": "1F53A",
+ "unicode_alternates": [],
+ "name": "up-pointing red triangle",
+ "shortname": ":small_red_triangle:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["shape"],
+ "moji": "🔺"
+ },
+ "small_red_triangle_down": {
+ "unicode": "1F53B",
+ "unicode_alternates": [],
+ "name": "down-pointing red triangle",
+ "shortname": ":small_red_triangle_down:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["shape"],
+ "moji": "🔻"
+ },
+ "smile": {
+ "unicode": "1F604",
+ "unicode_alternates": [],
+ "name": "smiling face with open mouth and smiling eyes",
+ "shortname": ":smile:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [":)", ":-)", "=]", "=)", ":]"],
+ "keywords": ["face", "funny", "haha", "happy", "joy", "laugh", "smile", "smiley", "smiling"],
+ "moji": "😄"
+ },
+ "smile_cat": {
+ "unicode": "1F638",
+ "unicode_alternates": [],
+ "name": "grinning cat face with smiling eyes",
+ "shortname": ":smile_cat:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "cats", "cat", "smile", "grin", "grinning"],
+ "moji": "😸"
+ },
+ "smiley": {
+ "unicode": "1F603",
+ "unicode_alternates": [],
+ "name": "smiling face with open mouth",
+ "shortname": ":smiley:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [":D", ":-D", "=D"],
+ "keywords": ["face", "haha", "happy", "joy", "smiling", "smile", "smiley"],
+ "moji": "😃"
+ },
+ "smiley_cat": {
+ "unicode": "1F63A",
+ "unicode_alternates": [],
+ "name": "smiling cat face with open mouth",
+ "shortname": ":smiley_cat:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "cats", "happy", "smile", "smiley", "cat", "happy"],
+ "moji": "😺"
+ },
+ "smiling_imp": {
+ "unicode": "1F608",
+ "unicode_alternates": [],
+ "name": "smiling face with horns",
+ "shortname": ":smiling_imp:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["devil", "horns", "horns", "devil", "impish", "trouble"],
+ "moji": "😈"
+ },
+ "smirk": {
+ "unicode": "1F60F",
+ "unicode_alternates": [],
+ "name": "smirking face",
+ "shortname": ":smirk:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["mean", "prank", "smile", "smug", "smirking", "smirk", "smug", "smile", "half-smile", "conceited"],
+ "moji": "😏"
+ },
+ "smirk_cat": {
+ "unicode": "1F63C",
+ "unicode_alternates": [],
+ "name": "cat face with wry smile",
+ "shortname": ":smirk_cat:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "cats", "smirk", "smirking", "wry", "confident", "confidence"],
+ "moji": "😼"
+ },
+ "smoking": {
+ "unicode": "1F6AC",
+ "unicode_alternates": [],
+ "name": "smoking symbol",
+ "shortname": ":smoking:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["cigarette", "kills", "tobacco", "smoking", "cigarette", "smoke", "cancer", "lungs", "inhale", "tar", "nicotine"],
+ "moji": "🚬"
+ },
+ "snail": {
+ "unicode": "1F40C",
+ "unicode_alternates": [],
+ "name": "snail",
+ "shortname": ":snail:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "shell", "slow", "snail", "slow", "escargot", "french", "appetizer"],
+ "moji": "🐌"
+ },
+ "snake": {
+ "unicode": "1F40D",
+ "unicode_alternates": [],
+ "name": "snake",
+ "shortname": ":snake:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "evil"],
+ "moji": "🐍"
+ },
+ "snowboarder": {
+ "unicode": "1F3C2",
+ "unicode_alternates": [],
+ "name": "snowboarder",
+ "shortname": ":snowboarder:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["sports", "winter", "snow", "boarding", "sports", "freestyle", "halfpipe", "board", "mountain", "alpine", "winter"],
+ "moji": "🏂"
+ },
+ "snowflake": {
+ "unicode": "2744",
+ "unicode_alternates": ["2744-FE0F"],
+ "name": "snowflake",
+ "shortname": ":snowflake:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["christmas", "cold", "season", "weather", "winter", "xmas", "snowflake", "snow", "frozen", "droplet", "ice", "crystal", "cold", "chilly", "winter", "unique", "special", "below zero", "elsa"],
+ "moji": "❄"
+ },
+ "snowman": {
+ "unicode": "26C4",
+ "unicode_alternates": ["26C4-FE0F"],
+ "name": "snowman without snow",
+ "shortname": ":snowman:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["christmas", "cold", "season", "weather", "winter", "xmas"],
+ "moji": "⛄"
+ },
+ "sob": {
+ "unicode": "1F62D",
+ "unicode_alternates": [],
+ "name": "loudly crying face",
+ "shortname": ":sob:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["cry", "face", "sad", "tears", "upset", "cry", "sob", "tears", "sad", "melancholy", "morn", "somber", "hurt"],
+ "moji": "😭"
+ },
+ "soccer": {
+ "unicode": "26BD",
+ "unicode_alternates": ["26BD-FE0F"],
+ "name": "soccer ball",
+ "shortname": ":soccer:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["balls", "fifa", "football", "sports", "european", "football"],
+ "moji": "⚽"
+ },
+ "soon": {
+ "unicode": "1F51C",
+ "unicode_alternates": [],
+ "name": "soon with rightwards arrow above",
+ "shortname": ":soon:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["arrow", "words"],
+ "moji": "🔜"
+ },
+ "sos": {
+ "unicode": "1F198",
+ "unicode_alternates": [],
+ "name": "squared sos",
+ "shortname": ":sos:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["emergency", "help", "red-square", "words"],
+ "moji": "🆘"
+ },
+ "sound": {
+ "unicode": "1F509",
+ "unicode_alternates": [],
+ "name": "speaker with one sound wave",
+ "shortname": ":sound:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["speaker", "volume"],
+ "moji": "🔉"
+ },
+ "space_invader": {
+ "unicode": "1F47E",
+ "unicode_alternates": [],
+ "name": "alien monster",
+ "shortname": ":space_invader:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["arcade", "game"],
+ "moji": "👾"
+ },
+ "spades": {
+ "unicode": "2660",
+ "unicode_alternates": ["2660-FE0F"],
+ "name": "black spade suit",
+ "shortname": ":spades:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["cards", "poker"],
+ "moji": "♠"
+ },
+ "spaghetti": {
+ "unicode": "1F35D",
+ "unicode_alternates": [],
+ "name": "spaghetti",
+ "shortname": ":spaghetti:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["food", "italian", "noodle", "spaghetti", "noodles", "tomato", "sauce", "italian"],
+ "moji": "🍝"
+ },
+ "sparkle": {
+ "unicode": "2747",
+ "unicode_alternates": ["2747-FE0F"],
+ "name": "sparkle",
+ "shortname": ":sparkle:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["green-square", "stars"],
+ "moji": "❇"
+ },
+ "sparkler": {
+ "unicode": "1F387",
+ "unicode_alternates": [],
+ "name": "firework sparkler",
+ "shortname": ":sparkler:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["night", "shine", "stars"],
+ "moji": "🎇"
+ },
+ "sparkles": {
+ "unicode": "2728",
+ "unicode_alternates": [],
+ "name": "sparkles",
+ "shortname": ":sparkles:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["cool", "shine", "shiny", "stars"],
+ "moji": "✨"
+ },
+ "sparkling_heart": {
+ "unicode": "1F496",
+ "unicode_alternates": [],
+ "name": "sparkling heart",
+ "shortname": ":sparkling_heart:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["affection", "like", "love", "valentines"],
+ "moji": "💖"
+ },
+ "speak_no_evil": {
+ "unicode": "1F64A",
+ "unicode_alternates": [],
+ "name": "speak-no-evil monkey",
+ "shortname": ":speak_no_evil:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "monkey", "monkey", "mouth", "talk", "say", "words", "verbal", "verbalize", "oral", "iwazaru"],
+ "moji": "🙊"
+ },
+ "speaker": {
+ "unicode": "1F508",
+ "unicode_alternates": [],
+ "name": "speaker",
+ "shortname": ":speaker:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["sound", "listen", "hear", "noise"]
+ },
+ "speaking_head": {
+ "unicode": "1F5E3",
+ "unicode_alternates": [],
+ "name": "speaking head in silhouette",
+ "shortname": ":speaking_head:",
+ "category": "objects_symbols",
+ "aliases": [":speaking_head_in_silhouette:"],
+ "aliases_ascii": [],
+ "keywords": ["talk"]
+ },
+ "speech_balloon": {
+ "unicode": "1F4AC",
+ "unicode_alternates": [],
+ "name": "speech balloon",
+ "shortname": ":speech_balloon:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["bubble", "words", "speech", "balloon", "talk", "conversation", "communication", "comic", "dialogue"],
+ "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",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["ship", "transportation", "vehicle", "motor", "speed", "ski", "power", "boat"],
+ "moji": "🚤"
+ },
+ "spider": {
+ "unicode": "1F577",
+ "unicode_alternates": [],
+ "name": "spider",
+ "shortname": ":spider:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["arachnid", "eight-legged"]
+ },
+ "spider_web": {
+ "unicode": "1F578",
+ "unicode_alternates": [],
+ "name": "spider web",
+ "shortname": ":spider_web:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["cobweb"]
+ },
+ "spy": {
+ "unicode": "1F575",
+ "unicode_alternates": [],
+ "name": "sleuth or spy",
+ "shortname": ":spy:",
+ "category": "people",
+ "aliases": [":sleuth_or_spy:"],
+ "aliases_ascii": [],
+ "keywords": ["pi", "undercover", "investigator"]
+ },
+ "stadium": {
+ "unicode": "1F3DF",
+ "unicode_alternates": [],
+ "name": "stadium",
+ "shortname": ":stadium:",
+ "category": "travel_places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["sport", "event", "concert", "convention", "game"]
+ },
+ "star": {
+ "unicode": "2B50",
+ "unicode_alternates": ["2B50-FE0F"],
+ "name": "white medium star",
+ "shortname": ":star:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["night", "yellow"],
+ "moji": "⭐"
+ },
+ "star2": {
+ "unicode": "1F31F",
+ "unicode_alternates": [],
+ "name": "glowing star",
+ "shortname": ":star2:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["night", "sparkle", "glow", "glowing", "star", "five", "points", "classic"],
+ "moji": "🌟"
+ },
+ "stars": {
+ "unicode": "1F320",
+ "unicode_alternates": [],
+ "name": "shooting star",
+ "shortname": ":stars:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["night", "photo", "shooting", "shoot", "star", "sky", "night", "comet", "meteoroid"],
+ "moji": "🌠"
+ },
+ "station": {
+ "unicode": "1F689",
+ "unicode_alternates": [],
+ "name": "station",
+ "shortname": ":station:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["public", "transportation", "vehicle", "station", "train", "subway"],
+ "moji": "🚉"
+ },
+ "statue_of_liberty": {
+ "unicode": "1F5FD",
+ "unicode_alternates": [],
+ "name": "statue of liberty",
+ "shortname": ":statue_of_liberty:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["american", "newyork"],
+ "moji": "🗽"
+ },
+ "steam_locomotive": {
+ "unicode": "1F682",
+ "unicode_alternates": [],
+ "name": "steam locomotive",
+ "shortname": ":steam_locomotive:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["train", "transportation", "vehicle", "locomotive", "steam", "train", "engine"],
+ "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",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["food", "meat", "stew", "hearty", "soup", "thick", "hot", "pot"],
+ "moji": "🍲"
+ },
+ "stock_chart": {
+ "unicode": "1F5E0",
+ "unicode_alternates": [],
+ "name": "stock chart",
+ "shortname": ":stock_chart:",
+ "category": "objects_symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["graph", "presentation", "stats", "business"]
+ },
+ "straight_ruler": {
+ "unicode": "1F4CF",
+ "unicode_alternates": [],
+ "name": "straight ruler",
+ "shortname": ":straight_ruler:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["stationery"],
+ "moji": "📏"
+ },
+ "strawberry": {
+ "unicode": "1F353",
+ "unicode_alternates": [],
+ "name": "strawberry",
+ "shortname": ":strawberry:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["food", "fruit", "nature", "strawberry", "short", "cake", "berry"],
+ "moji": "🍓"
+ },
+ "stuck_out_tongue": {
+ "unicode": "1F61B",
+ "unicode_alternates": [],
+ "name": "face with stuck-out tongue",
+ "shortname": ":stuck_out_tongue:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [":P", ":-P", "=P", ":-p", ":p", "=p", ":-Þ", ":Þ", ":þ", ":-þ", ":-b", ":b", "d:"],
+ "keywords": ["childish", "face", "mischievous", "playful", "prank", "tongue", "silly", "playful", "cheeky"],
+ "moji": "😛"
+ },
+ "stuck_out_tongue_closed_eyes": {
+ "unicode": "1F61D",
+ "unicode_alternates": [],
+ "name": "face with stuck-out tongue and tightly-closed eyes",
+ "shortname": ":stuck_out_tongue_closed_eyes:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["face", "mischievous", "playful", "prank", "tongue", "kidding", "silly", "playful", "ecstatic"],
+ "moji": "😝"
+ },
+ "stuck_out_tongue_winking_eye": {
+ "unicode": "1F61C",
+ "unicode_alternates": [],
+ "name": "face with stuck-out tongue and winking eye",
+ "shortname": ":stuck_out_tongue_winking_eye:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [">:P", "X-P", "x-p"],
+ "keywords": ["childish", "face", "mischievous", "playful", "prank", "tongue", "wink", "winking", "kidding", "silly", "playful", "crazy"],
+ "moji": "😜"
+ },
+ "sun_with_face": {
+ "unicode": "1F31E",
+ "unicode_alternates": [],
+ "name": "sun with face",
+ "shortname": ":sun_with_face:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["morning", "sun", "anthropomorphic", "face", "sky"],
+ "moji": "🌞"
+ },
+ "sunflower": {
+ "unicode": "1F33B",
+ "unicode_alternates": [],
+ "name": "sunflower",
+ "shortname": ":sunflower:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["nature", "plant", "sunflower", "sun", "flower", "seeds", "yellow"],
+ "moji": "🌻"
+ },
+ "sunglasses": {
+ "unicode": "1F60E",
+ "unicode_alternates": [],
+ "name": "smiling face with sunglasses",
+ "shortname": ":sunglasses:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": ["B-)", "B)", "8)", "8-)", "B-D", "8-D"],
+ "keywords": ["cool", "face", "smiling", "sunglasses", "sun", "glasses", "sunny", "cool", "smooth"],
+ "moji": "😎"
+ },
+ "sunny": {
+ "unicode": "2600",
+ "unicode_alternates": ["2600-FE0F"],
+ "name": "black sun with rays",
+ "shortname": ":sunny:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["brightness", "weather"]
+ },
+ "sunrise": {
+ "unicode": "1F305",
+ "unicode_alternates": [],
+ "name": "sunrise",
+ "shortname": ":sunrise:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["morning", "photo", "vacation", "view", "sunrise", "sun", "morning", "color", "sky"],
+ "moji": "🌅"
+ },
+ "sunrise_over_mountains": {
+ "unicode": "1F304",
+ "unicode_alternates": [],
+ "name": "sunrise over mountains",
+ "shortname": ":sunrise_over_mountains:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["photo", "vacation", "view", "sunrise", "sun", "morning", "mountain", "rural", "color", "sky"],
+ "moji": "🌄"
+ },
+ "surfer": {
+ "unicode": "1F3C4",
+ "unicode_alternates": [],
+ "name": "surfer",
+ "shortname": ":surfer:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["ocean", "sea", "sports", "surfer", "surf", "wave", "ocean", "ride", "swell"],
+ "moji": "🏄"
+ },
+ "sushi": {
+ "unicode": "1F363",
+ "unicode_alternates": [],
+ "name": "sushi",
+ "shortname": ":sushi:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["food", "japanese", "sushi", "fish", "raw", "nigiri", "japanese"],
+ "moji": "🍣"
+ },
+ "suspension_railway": {
+ "unicode": "1F69F",
+ "unicode_alternates": [],
+ "name": "suspension railway",
+ "shortname": ":suspension_railway:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["transportation", "vehicle", "suspension", "railway", "rail", "train", "transportation"],
+ "moji": "🚟"
+ },
+ "sweat": {
+ "unicode": "1F613",
+ "unicode_alternates": [],
+ "name": "face with cold sweat",
+ "shortname": ":sweat:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": ["':(", "':-(", "'=("],
+ "keywords": ["cold", "sweat", "sick", "anxious", "worried", "clammy", "diaphoresis", "face", "hot"],
+ "moji": "😓"
+ },
+ "sweat_drops": {
+ "unicode": "1F4A6",
+ "unicode_alternates": [],
+ "name": "splashing sweat symbol",
+ "shortname": ":sweat_drops:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["water"],
+ "moji": "💦"
+ },
+ "sweat_smile": {
+ "unicode": "1F605",
+ "unicode_alternates": [],
+ "name": "smiling face with open mouth and cold sweat",
+ "shortname": ":sweat_smile:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": ["':)", "':-)", "'=)", "':D", "':-D", "'=D"],
+ "keywords": ["face", "happy", "hot", "smiling", "cold", "sweat", "perspiration"],
+ "moji": "😅"
+ },
+ "sweet_potato": {
+ "unicode": "1F360",
+ "unicode_alternates": [],
+ "name": "roasted sweet potato",
+ "shortname": ":sweet_potato:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["food", "nature", "sweet", "potato", "potassium", "roasted", "roast"],
+ "moji": "🍠"
+ },
+ "swimmer": {
+ "unicode": "1F3CA",
+ "unicode_alternates": [],
+ "name": "swimmer",
+ "shortname": ":swimmer:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["sports", "swimmer", "swim", "water", "pool", "laps", "freestyle", "butterfly", "breaststroke", "backstroke"],
+ "moji": "🏊"
+ },
+ "symbols": {
+ "unicode": "1F523",
+ "unicode_alternates": [],
+ "name": "input symbol for symbols",
+ "shortname": ":symbols:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blue-square"],
+ "moji": "🔣"
+ },
+ "syringe": {
+ "unicode": "1F489",
+ "unicode_alternates": [],
+ "name": "syringe",
+ "shortname": ":syringe:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blood", "drugs", "health", "hospital", "medicine", "needle"],
+ "moji": "💉"
+ },
+ "tada": {
+ "unicode": "1F389",
+ "unicode_alternates": [],
+ "name": "party popper",
+ "shortname": ":tada:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["contulations", "party", "party", "popper", "tada", "celebration", "victory", "announcement", "climax", "congratulations"],
+ "moji": "🎉"
+ },
+ "tanabata_tree": {
+ "unicode": "1F38B",
+ "unicode_alternates": [],
+ "name": "tanabata tree",
+ "shortname": ":tanabata_tree:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["nature", "plant", "tanabata", "tree", "festival", "star", "wish", "holiday"],
+ "moji": "🎋"
+ },
+ "tangerine": {
+ "unicode": "1F34A",
+ "unicode_alternates": [],
+ "name": "tangerine",
+ "shortname": ":tangerine:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["food", "fruit", "nature", "tangerine", "citrus", "orange"],
+ "moji": "🍊"
+ },
+ "taurus": {
+ "unicode": "2649",
+ "unicode_alternates": ["2649-FE0F"],
+ "name": "taurus",
+ "shortname": ":taurus:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["purple-square", "sign", "taurus", "bull", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "zodiac", "horoscope"],
+ "moji": "♉"
+ },
+ "taxi": {
+ "unicode": "1F695",
+ "unicode_alternates": [],
+ "name": "taxi",
+ "shortname": ":taxi:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["cars", "transportation", "uber", "vehicle", "taxi", "car", "automobile", "city", "transport", "service"],
+ "moji": "🚕"
+ },
+ "tea": {
+ "unicode": "1F375",
+ "unicode_alternates": [],
+ "name": "teacup without handle",
+ "shortname": ":tea:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["bowl", "breakfast", "british", "drink", "green", "tea", "leaf", "drink", "teacup", "hot", "beverage"],
+ "moji": "🍵"
+ },
+ "telephone": {
+ "unicode": "260E",
+ "unicode_alternates": ["260E-FE0F"],
+ "name": "black telephone",
+ "shortname": ":telephone:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["communication", "dial", "technology"],
+ "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": [],
+ "name": "telephone receiver",
+ "shortname": ":telephone_receiver:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["communication", "dial", "technology"],
+ "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": [],
+ "name": "telescope",
+ "shortname": ":telescope:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["space", "stars"],
+ "moji": "🔭"
+ },
+ "tennis": {
+ "unicode": "1F3BE",
+ "unicode_alternates": [],
+ "name": "tennis racquet and ball",
+ "shortname": ":tennis:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["balls", "green", "sports", "tennis", "racket", "racquet", "ball", "game", "net", "court", "love"],
+ "moji": "🎾"
+ },
+ "tent": {
+ "unicode": "26FA",
+ "unicode_alternates": ["26FA-FE0F"],
+ "name": "tent",
+ "shortname": ":tent:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["camp", "outdoors", "photo"],
+ "moji": "⛺"
+ },
+ "thermometer": {
+ "unicode": "1F321",
+ "unicode_alternates": [],
+ "name": "thermometer",
+ "shortname": ":thermometer:",
+ "category": "objects_symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["temperature"]
+ },
+ "thought_balloon": {
+ "unicode": "1F4AD",
+ "unicode_alternates": [],
+ "name": "thought balloon",
+ "shortname": ":thought_balloon:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["bubble", "cloud", "speech", "thought", "balloon", "comic", "think", "day dream", "wonder"],
+ "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",
+ "shortname": ":three:",
+ "category": "other",
+ "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"]
+ },
+ "thumbsdown": {
+ "unicode": "1F44E",
+ "unicode_alternates": [],
+ "name": "thumbs down sign",
+ "shortname": ":thumbsdown:",
+ "category": "emoticons",
+ "aliases": [":-1:"],
+ "aliases_ascii": [],
+ "keywords": ["hand", "no"],
+ "moji": "👎"
+ },
+ "thumbsup": {
+ "unicode": "1F44D",
+ "unicode_alternates": [],
+ "name": "thumbs up sign",
+ "shortname": ":thumbsup:",
+ "category": "emoticons",
+ "aliases": [":+1:"],
+ "aliases_ascii": [],
+ "keywords": ["cool", "hand", "like", "yes"],
+ "moji": "👍"
+ },
+ "ticket": {
+ "unicode": "1F3AB",
+ "unicode_alternates": [],
+ "name": "ticket",
+ "shortname": ":ticket:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["concert", "event", "pass", "ticket", "show", "entertainment", "stub", "admission", "proof", "purchase"],
+ "moji": "🎫"
+ },
+ "tickets": {
+ "unicode": "1F39F",
+ "unicode_alternates": [],
+ "name": "admission tickets",
+ "shortname": ":tickets:",
+ "category": "activity",
+ "aliases": [":admission_tickets:"],
+ "aliases_ascii": [],
+ "keywords": ["concert", "event", "pass", "show", "entertainment", "stub", "proof", "purchase"]
+ },
+ "tiger": {
+ "unicode": "1F42F",
+ "unicode_alternates": [],
+ "name": "tiger face",
+ "shortname": ":tiger:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal"],
+ "moji": "🐯"
+ },
+ "tiger2": {
+ "unicode": "1F405",
+ "unicode_alternates": [],
+ "name": "tiger",
+ "shortname": ":tiger2:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "nature", "tiger", "cat", "striped", "tony", "tigger", "hobs"],
+ "moji": "🐅"
+ },
+ "tired_face": {
+ "unicode": "1F62B",
+ "unicode_alternates": [],
+ "name": "tired face",
+ "shortname": ":tired_face:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["face", "frustrated", "sick", "upset", "whine", "exhausted", "sleepy", "tired"],
+ "moji": "😫"
+ },
+ "toilet": {
+ "unicode": "1F6BD",
+ "unicode_alternates": [],
+ "name": "toilet",
+ "shortname": ":toilet:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["restroom", "wc", "toilet", "bathroom", "throne", "porcelain", "waste", "flush", "plumbing"],
+ "moji": "🚽"
+ },
+ "tokyo_tower": {
+ "unicode": "1F5FC",
+ "unicode_alternates": [],
+ "name": "tokyo tower",
+ "shortname": ":tokyo_tower:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["japan", "photo"],
+ "moji": "🗼"
+ },
+ "tomato": {
+ "unicode": "1F345",
+ "unicode_alternates": [],
+ "name": "tomato",
+ "shortname": ":tomato:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["food", "fruit", "nature", "vegetable", "tomato", "fruit", "sauce", "italian"],
+ "moji": "🍅"
+ },
+ "tongue": {
+ "unicode": "1F445",
+ "unicode_alternates": [],
+ "name": "tongue",
+ "shortname": ":tongue:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["mouth", "playful", "tongue", "mouth", "taste", "buds", "food", "silly", "playful", "tease", "kiss", "french kiss", "lick", "tasty", "playfulness", "silliness", "intimacy"],
+ "moji": "👅"
+ },
+ "tools": {
+ "unicode": "1F6E0",
+ "unicode_alternates": [],
+ "name": "hammer and wrench",
+ "shortname": ":tools:",
+ "category": "objects_symbols",
+ "aliases": [":hammer_and_wrench:"],
+ "aliases_ascii": [],
+ "keywords": ["tools"]
+ },
+ "top": {
+ "unicode": "1F51D",
+ "unicode_alternates": [],
+ "name": "top with upwards arrow above",
+ "shortname": ":top:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blue-square", "words"],
+ "moji": "🔝"
+ },
+ "tophat": {
+ "unicode": "1F3A9",
+ "unicode_alternates": [],
+ "name": "top hat",
+ "shortname": ":tophat:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["classy", "gentleman", "magic", "top", "hat", "cap", "beaver", "high", "tall", "stove", "pipe", "chimney", "topper", "london", "period piece", "magic", "magician"],
+ "moji": "🎩"
+ },
+ "trackball": {
+ "unicode": "1F5B2",
+ "unicode_alternates": [],
+ "name": "trackball",
+ "shortname": ":trackball:",
+ "category": "objects_symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["input", "device", "gadget"]
+ },
+ "tractor": {
+ "unicode": "1F69C",
+ "unicode_alternates": [],
+ "name": "tractor",
+ "shortname": ":tractor:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["agriculture", "car", "farming", "vehicle", "tractor", "farm", "construction", "machine", "digger"],
+ "moji": "🚜"
+ },
+ "traffic_light": {
+ "unicode": "1F6A5",
+ "unicode_alternates": [],
+ "name": "horizontal traffic light",
+ "shortname": ":traffic_light:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["traffic", "transportation", "traffic", "light", "stop", "go", "yield", "horizontal"],
+ "moji": "🚥"
+ },
+ "train": {
+ "unicode": "1F68B",
+ "unicode_alternates": [],
+ "name": "Tram Car",
+ "shortname": ":train:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["tram", "rail"]
+ },
+ "train2": {
+ "unicode": "1F686",
+ "unicode_alternates": [],
+ "name": "train",
+ "shortname": ":train2:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["transportation", "vehicle", "train", "locomotive", "rail"],
+ "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",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["transportation", "vehicle", "tram", "transportation", "transport"],
+ "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",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["triangle", "triangular", "flag", "golf", "post", "flagpole"],
+ "moji": "🚩"
+ },
+ "triangular_ruler": {
+ "unicode": "1F4D0",
+ "unicode_alternates": [],
+ "name": "triangular ruler",
+ "shortname": ":triangular_ruler:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["architect", "math", "sketch", "stationery"],
+ "moji": "📐"
+ },
+ "trident": {
+ "unicode": "1F531",
+ "unicode_alternates": [],
+ "name": "trident emblem",
+ "shortname": ":trident:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["spear", "weapon"],
+ "moji": "🔱"
+ },
+ "triumph": {
+ "unicode": "1F624",
+ "unicode_alternates": [],
+ "name": "face with look of triumph",
+ "shortname": ":triumph:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["face", "gas", "phew", "triumph", "steam", "breath"],
+ "moji": "😤"
+ },
+ "trolleybus": {
+ "unicode": "1F68E",
+ "unicode_alternates": [],
+ "name": "trolleybus",
+ "shortname": ":trolleybus:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["bart", "transportation", "vehicle", "trolley", "bus", "city", "transport", "transportation"],
+ "moji": "🚎"
+ },
+ "trophy": {
+ "unicode": "1F3C6",
+ "unicode_alternates": [],
+ "name": "trophy",
+ "shortname": ":trophy:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["award", "ceremony", "contest", "ftw", "place", "win", "trophy", "first", "show", "place", "win", "reward", "achievement", "medal"],
+ "moji": "🏆"
+ },
+ "tropical_drink": {
+ "unicode": "1F379",
+ "unicode_alternates": [],
+ "name": "tropical drink",
+ "shortname": ":tropical_drink:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["beverage", "tropical", "drink", "mixed", "pineapple", "coconut", "pina", "fruit", "umbrella"],
+ "moji": "🍹"
+ },
+ "tropical_fish": {
+ "unicode": "1F420",
+ "unicode_alternates": [],
+ "name": "tropical fish",
+ "shortname": ":tropical_fish:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "swim"],
+ "moji": "🐠"
+ },
+ "truck": {
+ "unicode": "1F69A",
+ "unicode_alternates": [],
+ "name": "delivery truck",
+ "shortname": ":truck:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["cars", "transportation", "truck", "delivery", "package"],
+ "moji": "🚚"
+ },
+ "trumpet": {
+ "unicode": "1F3BA",
+ "unicode_alternates": [],
+ "name": "trumpet",
+ "shortname": ":trumpet:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["brass", "music", "trumpet", "brass", "music", "instrument"],
+ "moji": "🎺"
+ },
+ "tulip": {
+ "unicode": "1F337",
+ "unicode_alternates": [],
+ "name": "tulip",
+ "shortname": ":tulip:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["flowers", "nature", "plant", "tulip", "flower", "bulb", "spring", "easter"],
+ "moji": "🌷"
+ },
+ "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"]
+ },
+ "turtle": {
+ "unicode": "1F422",
+ "unicode_alternates": [],
+ "name": "turtle",
+ "shortname": ":turtle:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "slow", "turtle", "shell", "tortoise", "chelonian", "reptile", "slow", "snap", "steady"],
+ "moji": "🐢"
+ },
+ "twisted_rightwards_arrows": {
+ "unicode": "1F500",
+ "unicode_alternates": [],
+ "name": "twisted rightwards arrows",
+ "shortname": ":twisted_rightwards_arrows:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blue-square"],
+ "moji": "🔀"
+ },
+ "two": {
+ "moji": "2️⃣",
+ "unicode": "0032-20E3",
+ "unicode_alternates": ["0032-FE0F-20E3"],
+ "name": "digit two",
+ "shortname": ":two:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["2", "blue-square", "numbers", "prime"]
+ },
+ "two_hearts": {
+ "unicode": "1F495",
+ "unicode_alternates": [],
+ "name": "two hearts",
+ "shortname": ":two_hearts:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["affection", "like", "love", "valentines", "heart", "hearts", "two", "love", "emotion"],
+ "moji": "💕"
+ },
+ "two_men_holding_hands": {
+ "unicode": "1F46C",
+ "unicode_alternates": [],
+ "name": "two men holding hands",
+ "shortname": ":two_men_holding_hands:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["bromance", "couple", "friends", "like", "love", "men", "gay", "homosexual", "friends", "hands", "holding", "team", "unity"],
+ "moji": "👬"
+ },
+ "two_women_holding_hands": {
+ "unicode": "1F46D",
+ "unicode_alternates": [],
+ "name": "two women holding hands",
+ "shortname": ":two_women_holding_hands:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["couple", "female", "friends", "like", "love", "women", "hands", "girlfriends", "friends", "sisters", "mother", "daughter", "gay", "homosexual", "couple", "unity"],
+ "moji": "👭"
+ },
+ "u5272": {
+ "unicode": "1F239",
+ "unicode_alternates": [],
+ "name": "squared cjk unified ideograph-5272",
+ "shortname": ":u5272:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["chinese", "cut", "divide", "kanji", "pink"],
+ "moji": "🈹"
+ },
+ "u5408": {
+ "unicode": "1F234",
+ "unicode_alternates": [],
+ "name": "squared cjk unified ideograph-5408",
+ "shortname": ":u5408:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["chinese", "japanese", "join", "kanji"],
+ "moji": "🈴"
+ },
+ "u55b6": {
+ "unicode": "1F23A",
+ "unicode_alternates": [],
+ "name": "squared cjk unified ideograph-55b6",
+ "shortname": ":u55b6:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["japanese", "opening hours"],
+ "moji": "🈺"
+ },
+ "u6307": {
+ "unicode": "1F22F",
+ "unicode_alternates": ["1F22F-FE0F"],
+ "name": "squared cjk unified ideograph-6307",
+ "shortname": ":u6307:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["chinese", "green-square", "kanji", "point"],
+ "moji": "🈯"
+ },
+ "u6708": {
+ "unicode": "1F237",
+ "unicode_alternates": [],
+ "name": "squared cjk unified ideograph-6708",
+ "shortname": ":u6708:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["chinese", "japanese", "kanji", "moon", "orange-square"],
+ "moji": "🈷"
+ },
+ "u6709": {
+ "unicode": "1F236",
+ "unicode_alternates": [],
+ "name": "squared cjk unified ideograph-6709",
+ "shortname": ":u6709:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["chinese", "have", "kanji", "orange-square"],
+ "moji": "🈶"
+ },
+ "u6e80": {
+ "unicode": "1F235",
+ "unicode_alternates": [],
+ "name": "squared cjk unified ideograph-6e80",
+ "shortname": ":u6e80:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["chinese", "full", "japanese", "kanji", "red-square"],
+ "moji": "🈵"
+ },
+ "u7121": {
+ "unicode": "1F21A",
+ "unicode_alternates": ["1F21A-FE0F"],
+ "name": "squared cjk unified ideograph-7121",
+ "shortname": ":u7121:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["chinese", "japanese", "kanji", "no", "nothing", "orange-square"],
+ "moji": "🈚"
+ },
+ "u7533": {
+ "unicode": "1F238",
+ "unicode_alternates": [],
+ "name": "squared cjk unified ideograph-7533",
+ "shortname": ":u7533:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["chinese", "japanese", "kanji"],
+ "moji": "🈸"
+ },
+ "u7981": {
+ "unicode": "1F232",
+ "unicode_alternates": [],
+ "name": "squared cjk unified ideograph-7981",
+ "shortname": ":u7981:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["chinese", "forbidden", "japanese", "kanji", "limit", "restricted"],
+ "moji": "🈲"
+ },
+ "u7a7a": {
+ "unicode": "1F233",
+ "unicode_alternates": [],
+ "name": "squared cjk unified ideograph-7a7a",
+ "shortname": ":u7a7a:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["chinese", "empty", "japanese", "kanji"],
+ "moji": "🈳"
+ },
+ "umbrella": {
+ "unicode": "2614",
+ "unicode_alternates": ["2614-FE0F"],
+ "name": "umbrella with rain drops",
+ "shortname": ":umbrella:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["rain", "weather"],
+ "moji": "☔"
+ },
+ "unamused": {
+ "unicode": "1F612",
+ "unicode_alternates": [],
+ "name": "unamused face",
+ "shortname": ":unamused:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["bored", "face", "indifference", "serious", "straight face", "unamused", "not amused", "depressed", "unhappy", "disapprove", "lame"],
+ "moji": "😒"
+ },
+ "underage": {
+ "unicode": "1F51E",
+ "unicode_alternates": [],
+ "name": "no one under eighteen symbol",
+ "shortname": ":underage:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["18", "drink", "night", "pub"],
+ "moji": "🔞"
+ },
+ "unlock": {
+ "unicode": "1F513",
+ "unicode_alternates": [],
+ "name": "open lock",
+ "shortname": ":unlock:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["privacy", "security"],
+ "moji": "🔓"
+ },
+ "up": {
+ "unicode": "1F199",
+ "unicode_alternates": [],
+ "name": "squared up with exclamation mark",
+ "shortname": ":up:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blue-square"],
+ "moji": "🆙"
+ },
+ "v": {
+ "unicode": "270C",
+ "unicode_alternates": ["270C-FE0F"],
+ "name": "victory hand",
+ "shortname": ":v:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["fingers", "hand", "ohyeah", "peace", "two", "victory"],
+ "moji": "✌"
+ },
+ "vertical_traffic_light": {
+ "unicode": "1F6A6",
+ "unicode_alternates": [],
+ "name": "vertical traffic light",
+ "shortname": ":vertical_traffic_light:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["transportation", "traffic", "light", "stop", "go", "yield", "vertical"],
+ "moji": "🚦"
+ },
+ "vhs": {
+ "unicode": "1F4FC",
+ "unicode_alternates": [],
+ "name": "videocassette",
+ "shortname": ":vhs:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["oldschool", "record", "video"],
+ "moji": "📼"
+ },
+ "vibration_mode": {
+ "unicode": "1F4F3",
+ "unicode_alternates": [],
+ "name": "vibration mode",
+ "shortname": ":vibration_mode:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["orange-square", "phone"],
+ "moji": "📳"
+ },
+ "video_camera": {
+ "unicode": "1F4F9",
+ "unicode_alternates": [],
+ "name": "video camera",
+ "shortname": ":video_camera:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["film", "record"],
+ "moji": "📹"
+ },
+ "video_game": {
+ "unicode": "1F3AE",
+ "unicode_alternates": [],
+ "name": "video game",
+ "shortname": ":video_game:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["PS4", "console", "controller", "play", "video", "game", "console", "controller", "nintendo", "xbox", "playstation"],
+ "moji": "🎮"
+ },
+ "violin": {
+ "unicode": "1F3BB",
+ "unicode_alternates": [],
+ "name": "violin",
+ "shortname": ":violin:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["instrument", "music", "violin", "fiddle", "music", "instrument"],
+ "moji": "🎻"
+ },
+ "virgo": {
+ "unicode": "264D",
+ "unicode_alternates": ["264D-FE0F"],
+ "name": "virgo",
+ "shortname": ":virgo:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["sign", "virgo", "maiden", "astrology", "greek", "constellation", "stars", "zodiac", "sign", "zodiac", "horoscope"],
+ "moji": "♍"
+ },
+ "volcano": {
+ "unicode": "1F30B",
+ "unicode_alternates": [],
+ "name": "volcano",
+ "shortname": ":volcano:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["nature", "photo", "volcano", "lava", "magma", "hot", "explode"],
+ "moji": "🌋"
+ },
+ "vs": {
+ "unicode": "1F19A",
+ "unicode_alternates": [],
+ "name": "squared vs",
+ "shortname": ":vs:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["orange-square", "words"],
+ "moji": "🆚"
+ },
+ "vulcan": {
+ "unicode": "1F596",
+ "unicode_alternates": [],
+ "name": "raised hand with part between middle and ring fingers",
+ "shortname": ":vulcan:",
+ "category": "people",
+ "aliases": [":raised_hand_with_part_between_middle_and_ring_fingers:"],
+ "aliases_ascii": [],
+ "keywords": ["vulcan", "spock", "leonard", "nimoy", "star trek", "live long"]
+ },
+ "walking": {
+ "unicode": "1F6B6",
+ "unicode_alternates": [],
+ "name": "pedestrian",
+ "shortname": ":walking:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["human", "man", "walk", "pedestrian", "stroll", "stride", "foot", "feet"],
+ "moji": "🚶"
+ },
+ "waning_crescent_moon": {
+ "unicode": "1F318",
+ "unicode_alternates": [],
+ "name": "waning crescent moon symbol",
+ "shortname": ":waning_crescent_moon:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["nature", "moon", "crescent", "waning", "sky", "night", "cheese", "phase"],
+ "moji": "🌘"
+ },
+ "waning_gibbous_moon": {
+ "unicode": "1F316",
+ "unicode_alternates": [],
+ "name": "waning gibbous moon symbol",
+ "shortname": ":waning_gibbous_moon:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["nature", "moon", "waning", "gibbous", "sky", "night", "cheese", "phase"],
+ "moji": "🌖"
+ },
+ "warning": {
+ "unicode": "26A0",
+ "unicode_alternates": ["26A0-FE0F"],
+ "name": "warning sign",
+ "shortname": ":warning:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["exclamation", "wip"],
+ "moji": "⚠"
+ },
+ "wastebasket": {
+ "unicode": "1F5D1",
+ "unicode_alternates": [],
+ "name": "wastebasket",
+ "shortname": ":wastebasket:",
+ "category": "objects_symbols",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["trash", "garbage", "dispose"]
+ },
+ "watch": {
+ "unicode": "231A",
+ "unicode_alternates": ["231A-FE0F"],
+ "name": "watch",
+ "shortname": ":watch:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["accessories", "time"],
+ "moji": "⌚"
+ },
+ "water_buffalo": {
+ "unicode": "1F403",
+ "unicode_alternates": [],
+ "name": "water buffalo",
+ "shortname": ":water_buffalo:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "cow", "nature", "ox", "water", "buffalo", "asia", "bovine", "milk", "dairy"],
+ "moji": "🐃"
+ },
+ "watermelon": {
+ "unicode": "1F349",
+ "unicode_alternates": [],
+ "name": "watermelon",
+ "shortname": ":watermelon:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["food", "fruit", "melon", "watermelon", "summer", "fruit", "large"],
+ "moji": "🍉"
+ },
+ "wave": {
+ "unicode": "1F44B",
+ "unicode_alternates": [],
+ "name": "waving hand sign",
+ "shortname": ":wave:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["farewell", "gesture", "goodbye", "hands", "solong"],
+ "moji": "👋"
+ },
+ "wavy_dash": {
+ "unicode": "3030",
+ "unicode_alternates": [],
+ "name": "wavy dash",
+ "shortname": ":wavy_dash:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["draw", "line"],
+ "moji": "〰"
+ },
+ "waxing_crescent_moon": {
+ "unicode": "1F312",
+ "unicode_alternates": [],
+ "name": "waxing crescent moon symbol",
+ "shortname": ":waxing_crescent_moon:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["nature", "moon", "waxing", "sky", "night", "cheese", "phase"],
+ "moji": "🌒"
+ },
+ "waxing_gibbous_moon": {
+ "unicode": "1F314",
+ "unicode_alternates": [],
+ "name": "waxing gibbous moon symbol",
+ "shortname": ":waxing_gibbous_moon:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["nature"],
+ "moji": "🌔"
+ },
+ "wc": {
+ "unicode": "1F6BE",
+ "unicode_alternates": [],
+ "name": "water closet",
+ "shortname": ":wc:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blue-square", "restroom", "toilet", "water", "closet", "toilet", "bathroom", "throne", "porcelain", "waste", "flush", "plumbing"],
+ "moji": "🚾"
+ },
+ "weary": {
+ "unicode": "1F629",
+ "unicode_alternates": [],
+ "name": "weary face",
+ "shortname": ":weary:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["face", "frustrated", "sad", "sleepy", "tired", "weary", "sleepy", "tired", "tiredness", "study", "finals", "school", "exhausted"],
+ "moji": "😩"
+ },
+ "wedding": {
+ "unicode": "1F492",
+ "unicode_alternates": [],
+ "name": "wedding",
+ "shortname": ":wedding:",
+ "category": "places",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["affection", "bride", "couple", "groom", "like", "love", "marriage"],
+ "moji": "💒"
+ },
+ "whale": {
+ "unicode": "1F433",
+ "unicode_alternates": [],
+ "name": "spouting whale",
+ "shortname": ":whale:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "nature", "ocean", "sea"],
+ "moji": "🐳"
+ },
+ "whale2": {
+ "unicode": "1F40B",
+ "unicode_alternates": [],
+ "name": "whale",
+ "shortname": ":whale2:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "nature", "ocean", "sea", "whale", "blubber", "bloated", "fat", "large", "massive"],
+ "moji": "🐋"
+ },
+ "wheelchair": {
+ "unicode": "267F",
+ "unicode_alternates": ["267F-FE0F"],
+ "name": "wheelchair symbol",
+ "shortname": ":wheelchair:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blue-square", "disabled"],
+ "moji": "♿"
+ },
+ "white_check_mark": {
+ "unicode": "2705",
+ "unicode_alternates": [],
+ "name": "white heavy check mark",
+ "shortname": ":white_check_mark:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["agree", "green-square", "ok"],
+ "moji": "✅"
+ },
+ "white_circle": {
+ "unicode": "26AA",
+ "unicode_alternates": ["26AA-FE0F"],
+ "name": "medium white circle",
+ "shortname": ":white_circle:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["shape"],
+ "moji": "⚪"
+ },
+ "white_flower": {
+ "unicode": "1F4AE",
+ "unicode_alternates": [],
+ "name": "white flower",
+ "shortname": ":white_flower:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["japanese", "white", "flower", "teacher", "school", "grade", "score", "brilliance", "intelligence", "homework", "student", "assignment", "praise"],
+ "moji": "💮"
+ },
+ "white_large_square": {
+ "unicode": "2B1C",
+ "unicode_alternates": ["2B1C-FE0F"],
+ "name": "white large square",
+ "shortname": ":white_large_square:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["shape"],
+ "moji": "⬜"
+ },
+ "white_medium_small_square": {
+ "unicode": "25FD",
+ "unicode_alternates": ["25FD-FE0F"],
+ "name": "white medium small square",
+ "shortname": ":white_medium_small_square:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["shape"],
+ "moji": "◽"
+ },
+ "white_medium_square": {
+ "unicode": "25FB",
+ "unicode_alternates": ["25FB-FE0F"],
+ "name": "white medium square",
+ "shortname": ":white_medium_square:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["shape"],
+ "moji": "◻"
+ },
+ "white_small_square": {
+ "unicode": "25AB",
+ "unicode_alternates": ["25AB-FE0F"],
+ "name": "white small square",
+ "shortname": ":white_small_square:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["shape"],
+ "moji": "▫"
+ },
+ "white_square_button": {
+ "unicode": "1F533",
+ "unicode_alternates": [],
+ "name": "white square button",
+ "shortname": ":white_square_button:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["shape"],
+ "moji": "🔳"
+ },
+ "wind_blowing_face": {
+ "unicode": "1F32C",
+ "unicode_alternates": [],
+ "name": "wind blowing face",
+ "shortname": ":wind_blowing_face:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["mother", "nature"]
+ },
+ "wind_chime": {
+ "unicode": "1F390",
+ "unicode_alternates": [],
+ "name": "wind chime",
+ "shortname": ":wind_chime:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["ding", "nature", "wind", "chime", "bell", "fūrin", "instrument", "music", "spirits", "soothing", "protective", "spiritual", "sound"],
+ "moji": "🎐"
+ },
+ "wine_glass": {
+ "unicode": "1F377",
+ "unicode_alternates": [],
+ "name": "wine glass",
+ "shortname": ":wine_glass:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["alcohol", "beverage", "booze", "bottle", "drink", "drunk", "fermented", "glass", "grapes", "tasting", "wine", "winery"],
+ "moji": "🍷"
+ },
+ "wink": {
+ "unicode": "1F609",
+ "unicode_alternates": [],
+ "name": "winking face",
+ "shortname": ":wink:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [";)", ";-)", "*-)", "*)", ";-]", ";]", ";D", ";^)"],
+ "keywords": ["face", "happy", "mischievous", "secret", "wink", "winking", "friendly", "joke"],
+ "moji": "😉"
+ },
+ "wolf": {
+ "unicode": "1F43A",
+ "unicode_alternates": [],
+ "name": "wolf face",
+ "shortname": ":wolf:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["animal", "nature"],
+ "moji": "🐺"
+ },
+ "woman": {
+ "unicode": "1F469",
+ "unicode_alternates": [],
+ "name": "woman",
+ "shortname": ":woman:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["female", "girls"],
+ "moji": "👩"
+ },
+ "womans_clothes": {
+ "unicode": "1F45A",
+ "unicode_alternates": [],
+ "name": "womans clothes",
+ "shortname": ":womans_clothes:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["fashion", "woman", "clothing", "clothes", "blouse", "shirt", "wardrobe", "breasts", "cleavage", "shopping", "shop", "dressing", "dressed"],
+ "moji": "👚"
+ },
+ "womans_hat": {
+ "unicode": "1F452",
+ "unicode_alternates": [],
+ "name": "womans hat",
+ "shortname": ":womans_hat:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["accessories", "fashion", "female"],
+ "moji": "👒"
+ },
+ "womens": {
+ "unicode": "1F6BA",
+ "unicode_alternates": [],
+ "name": "womens symbol",
+ "shortname": ":womens:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["purple-square", "woman", "bathroom", "restroom", "sign", "girl", "female", "avatar"],
+ "moji": "🚺"
+ },
+ "worried": {
+ "unicode": "1F61F",
+ "unicode_alternates": [],
+ "name": "worried face",
+ "shortname": ":worried:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["concern", "face", "nervous", "worried", "anxious", "distressed", "nervous", "tense"],
+ "moji": "😟"
+ },
+ "wrench": {
+ "unicode": "1F527",
+ "unicode_alternates": [],
+ "name": "wrench",
+ "shortname": ":wrench:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["diy", "ikea", "tools"],
+ "moji": "🔧"
+ },
+ "writing_hand": {
+ "unicode": "1F58E",
+ "unicode_alternates": [],
+ "name": "left writing hand",
+ "shortname": ":writing_hand:",
+ "category": "people",
+ "aliases": [":left_writing_hand:"],
+ "aliases_ascii": [],
+ "keywords": ["write", "sign", "signature", "draw"]
+ },
+ "x": {
+ "unicode": "274C",
+ "unicode_alternates": [],
+ "name": "cross mark",
+ "shortname": ":x:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["delete", "no", "remove"],
+ "moji": "❌"
+ },
+ "yellow_heart": {
+ "unicode": "1F49B",
+ "unicode_alternates": [],
+ "name": "yellow heart",
+ "shortname": ":yellow_heart:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["affection", "like", "love", "valentines", "yellow", "gold", "heart", "love", "friendship", "happy", "happiness", "trust", "compassionate", "respectful", "honest", "caring", "selfless"],
+ "moji": "💛"
+ },
+ "yen": {
+ "unicode": "1F4B4",
+ "unicode_alternates": [],
+ "name": "banknote with yen sign",
+ "shortname": ":yen:",
+ "category": "objects",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["currency", "dollar", "japanese", "money", "yen", "japan", "japanese", "banknote", "money", "currency", "paper", "cash", "bill"],
+ "moji": "💴"
+ },
+ "yum": {
+ "unicode": "1F60B",
+ "unicode_alternates": [],
+ "name": "face savouring delicious food",
+ "shortname": ":yum:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["face", "happy", "joy", "smile", "tongue", "delicious", "savoring", "food", "eat", "yummy", "yum", "tasty", "savory"],
+ "moji": "😋"
+ },
+ "zap": {
+ "unicode": "26A1",
+ "unicode_alternates": ["26A1-FE0F"],
+ "name": "high voltage sign",
+ "shortname": ":zap:",
+ "category": "nature",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["lightning bolt", "thunder", "weather"],
+ "moji": "⚡"
+ },
+ "zero": {
+ "moji": "0️⃣",
+ "unicode": "0030-20E3",
+ "unicode_alternates": ["0030-FE0F-20E3"],
+ "name": "digit zero",
+ "shortname": ":zero:",
+ "category": "other",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["blue-square", "null", "numbers"]
+ },
+ "zzz": {
+ "unicode": "1F4A4",
+ "unicode_alternates": [],
+ "name": "sleeping symbol",
+ "shortname": ":zzz:",
+ "category": "emoticons",
+ "aliases": [],
+ "aliases_ascii": [],
+ "keywords": ["sleepy", "tired"],
+ "moji": "💤"
+ }
+}
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index b1cd80bdf65..26e7c956e8f 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -67,9 +67,10 @@ module API
expose :shared_runners_enabled
expose :creator_id
expose :namespace
- expose :forked_from_project, using: Entities::ForkedFromProject, if: lambda{ | project, options | project.forked? }
+ expose :forked_from_project, using: Entities::ForkedFromProject, if: lambda{ |project, options| project.forked? }
expose :avatar_url
expose :star_count, :forks_count
+ expose :open_issues_count, if: lambda { |project, options| project.issues_enabled? && project.default_issues_tracker? }
end
class ProjectMember < UserBasic
@@ -165,7 +166,6 @@ module API
class MergeRequest < ProjectEntity
expose :target_branch, :source_branch
- # deprecated, always returns 0
expose :upvotes, :downvotes
expose :author, :assignee, using: Entities::UserBasic
expose :source_project_id, :target_project_id
diff --git a/lib/api/files.rb b/lib/api/files.rb
index a7a768f8895..8ad2c1883c7 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -7,7 +7,7 @@ module API
def commit_params(attrs)
{
file_path: attrs[:file_path],
- current_branch: attrs[:branch_name],
+ source_branch: attrs[:branch_name],
target_branch: attrs[:branch_name],
commit_message: attrs[:commit_message],
file_content: attrs[:content],
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index bdf4b77596e..a9e0960872a 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -25,7 +25,7 @@ module API
@projects = current_user.authorized_projects
@projects = filter_projects(@projects)
@projects = paginate @projects
- present @projects, with: Entities::Project
+ present @projects, with: Entities::ProjectWithAccess, user: current_user
end
# Get an owned projects list for authenticated user
@@ -36,6 +36,17 @@ module API
@projects = current_user.owned_projects
@projects = filter_projects(@projects)
@projects = paginate @projects
+ present @projects, with: Entities::ProjectWithAccess, user: current_user
+ end
+
+ # Gets starred project for the authenticated user
+ #
+ # Example Request:
+ # GET /projects/starred
+ get '/starred' do
+ @projects = current_user.starred_projects
+ @projects = filter_projects(@projects)
+ @projects = paginate @projects
present @projects, with: Entities::Project
end
@@ -48,7 +59,7 @@ module API
@projects = Project.all
@projects = filter_projects(@projects)
@projects = paginate @projects
- present @projects, with: Entities::Project
+ present @projects, with: Entities::ProjectWithAccess, user: current_user
end
# Get a single project
diff --git a/lib/api/users.rb b/lib/api/users.rb
index a98d668e02d..3400f0713ef 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -8,11 +8,17 @@ module API
#
# Example Request:
# GET /users
+ # GET /users?search=Admin
+ # GET /users?username=root
get do
- @users = User.all
- @users = @users.active if params[:active].present?
- @users = @users.search(params[:search]) if params[:search].present?
- @users = paginate @users
+ if params[:username].present?
+ @users = User.where(username: params[:username])
+ else
+ @users = User.all
+ @users = @users.active if params[:active].present?
+ @users = @users.search(params[:search]) if params[:search].present?
+ @users = paginate @users
+ end
if current_user.is_admin?
present @users, with: Entities::UserFull
diff --git a/lib/award_emoji.rb b/lib/award_emoji.rb
index 4d99164bc33..783fcfb61ad 100644
--- a/lib/award_emoji.rb
+++ b/lib/award_emoji.rb
@@ -1,47 +1,51 @@
class AwardEmoji
- EMOJI_LIST = [
- "+1", "-1", "100", "blush", "heart", "smile", "rage",
- "beers", "disappointed", "ok_hand",
- "helicopter", "shit", "airplane", "alarm_clock",
- "ambulance", "anguished", "two_hearts", "wink"
- ]
-
- ALIASES = {
- pout: "rage",
- satisfied: "laughing",
- hankey: "shit",
- poop: "shit",
- collision: "boom",
- thumbsup: "+1",
- thumbsdown: "-1",
- punch: "facepunch",
- raised_hand: "hand",
- running: "runner",
- ng_woman: "no_good",
- shoe: "mans_shoe",
- tshirt: "shirt",
- honeybee: "bee",
- flipper: "dolphin",
- paw_prints: "feet",
- waxing_gibbous_moon: "moon",
- telephone: "phone",
- knife: "hocho",
- envelope: "email",
- pencil: "memo",
- open_book: "book",
- sailboat: "boat",
- red_car: "car",
- lantern: "izakaya_lantern",
- uk: "gb",
- heavy_exclamation_mark: "exclamation",
- squirrel: "shipit"
+ CATEGORIES = {
+ other: "Other",
+ objects: "Objects",
+ places: "Places",
+ travel_places: "Travel",
+ emoticons: "Emoticons",
+ objects_symbols: "Symbols",
+ nature: "Nature",
+ celebration: "Celebration",
+ people: "People",
+ activity: "Activity",
+ flags: "Flags",
+ food_drink: "Food"
}.with_indifferent_access
- def self.path_to_emoji_image(name)
- "emoji/#{Emoji.emoji_filename(name)}.png"
+ def self.normilize_emoji_name(name)
+ aliases[name] || name
end
- def self.normilize_emoji_name(name)
- ALIASES[name] || name
+ def self.emoji_by_category
+ unless @emoji_by_category
+ @emoji_by_category = {}
+
+ emojis.each do |emoji_name, data|
+ data["name"] = emoji_name
+
+ @emoji_by_category[data["category"]] ||= []
+ @emoji_by_category[data["category"]] << data
+ end
+
+ @emoji_by_category = @emoji_by_category.sort.to_h
+ end
+
+ @emoji_by_category
+ end
+
+ def self.emojis
+ @emojis ||= begin
+ json_path = File.join(Rails.root, 'fixtures', 'emojis', 'index.json' )
+ JSON.parse(File.read(json_path))
+ end
+ end
+
+ def self.aliases
+ @aliases ||= begin
+ json_path = File.join(Rails.root, 'fixtures', 'emojis', 'aliases.json' )
+ JSON.parse(File.read(json_path))
+ end
end
end
diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb
index bdaa4721b4b..63ad8910c0f 100644
--- a/lib/banzai/filter/abstract_reference_filter.rb
+++ b/lib/banzai/filter/abstract_reference_filter.rb
@@ -98,7 +98,7 @@ module Banzai
project = project_from_ref(project_ref)
if project && object = find_object(project, id)
- title = escape_once(object_link_title(object))
+ title = object_link_title(object)
klass = reference_class(object_sym)
data = data_attribute(
@@ -110,17 +110,11 @@ module Banzai
url = matches[:url] if matches.names.include?("url")
url ||= url_for_object(object, project)
- text = link_text
- unless text
- text = object.reference_link_text(context[:project])
-
- extras = object_link_text_extras(object, matches)
- text += " (#{extras.join(", ")})" if extras.any?
- end
+ text = link_text || object_link_text(object, matches)
%(<a href="#{url}" #{data}
- title="#{title}"
- class="#{klass}">#{text}</a>)
+ title="#{escape_once(title)}"
+ class="#{klass}">#{escape_once(text)}</a>)
else
match
end
@@ -140,6 +134,15 @@ module Banzai
def object_link_title(object)
"#{object_class.name.titleize}: #{object.title}"
end
+
+ def object_link_text(object, matches)
+ text = object.reference_link_text(context[:project])
+
+ extras = object_link_text_extras(object, matches)
+ text += " (#{extras.join(", ")})" if extras.any?
+
+ text
+ end
end
end
end
diff --git a/lib/banzai/filter/external_issue_reference_filter.rb b/lib/banzai/filter/external_issue_reference_filter.rb
index f5737a7ac19..6136e73c096 100644
--- a/lib/banzai/filter/external_issue_reference_filter.rb
+++ b/lib/banzai/filter/external_issue_reference_filter.rb
@@ -23,6 +23,18 @@ module Banzai
end
end
+ def self.referenced_by(node)
+ project = Project.find(node.attr("data-project")) rescue nil
+ return unless project
+
+ id = node.attr("data-external-issue")
+ external_issue = ExternalIssue.new(id, project)
+
+ return unless external_issue
+
+ { external_issue: external_issue }
+ end
+
def call
# Early return if the project isn't using an external tracker
return doc if project.nil? || project.default_issues_tracker?
@@ -46,18 +58,20 @@ module Banzai
def issue_link_filter(text, link_text: nil)
project = context[:project]
- self.class.references_in(text) do |match, issue|
- url = url_for_issue(issue, project, only_path: context[:only_path])
+ self.class.references_in(text) do |match, id|
+ ExternalIssue.new(id, project)
+
+ url = url_for_issue(id, project, only_path: context[:only_path])
- title = escape_once("Issue in #{project.external_issue_tracker.title}")
+ title = "Issue in #{project.external_issue_tracker.title}"
klass = reference_class(:issue)
- data = data_attribute(project: project.id)
+ data = data_attribute(project: project.id, external_issue: id)
text = link_text || match
%(<a href="#{url}" #{data}
- title="#{title}"
- class="#{klass}">#{text}</a>)
+ title="#{escape_once(title)}"
+ class="#{klass}">#{escape_once(text)}</a>)
end
end
diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb
index 07bac2dd7fd..a3a7a23c1e6 100644
--- a/lib/banzai/filter/label_reference_filter.rb
+++ b/lib/banzai/filter/label_reference_filter.rb
@@ -60,7 +60,7 @@ module Banzai
text = link_text || render_colored_label(label)
%(<a href="#{url}" #{data}
- class="#{klass}">#{text}</a>)
+ class="#{klass}">#{escape_once(text)}</a>)
else
match
end
diff --git a/lib/banzai/filter/markdown_filter.rb b/lib/banzai/filter/markdown_filter.rb
index 0072bab1f99..d09cf41df39 100644
--- a/lib/banzai/filter/markdown_filter.rb
+++ b/lib/banzai/filter/markdown_filter.rb
@@ -6,7 +6,7 @@ module Banzai
class MarkdownFilter < HTML::Pipeline::TextFilter
def initialize(text, context = nil, result = nil)
super text, context, result
- @text = @text.gsub "\r", ''
+ @text = @text.delete "\r"
end
def call
diff --git a/lib/banzai/filter/redactor_filter.rb b/lib/banzai/filter/redactor_filter.rb
index 89e7a79789a..f01a32b5ae5 100644
--- a/lib/banzai/filter/redactor_filter.rb
+++ b/lib/banzai/filter/redactor_filter.rb
@@ -11,7 +11,7 @@ module Banzai
class RedactorFilter < HTML::Pipeline::Filter
def call
doc.css('a.gfm').each do |node|
- unless user_can_reference?(node)
+ unless user_can_see_reference?(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
@@ -24,12 +24,12 @@ module Banzai
private
- def user_can_reference?(node)
+ def user_can_see_reference?(node)
if node.has_attribute?('data-reference-filter')
reference_type = node.attr('data-reference-filter')
reference_filter = Banzai::Filter.const_get(reference_type)
- reference_filter.user_can_reference?(current_user, node, context)
+ reference_filter.user_can_see_reference?(current_user, node, context)
else
true
end
diff --git a/lib/banzai/filter/reference_filter.rb b/lib/banzai/filter/reference_filter.rb
index 33457a3f361..8ca05ace88c 100644
--- a/lib/banzai/filter/reference_filter.rb
+++ b/lib/banzai/filter/reference_filter.rb
@@ -12,7 +12,7 @@ module Banzai
# :project (required) - Current project, ignored if reference is cross-project.
# :only_path - Generate path-only links.
class ReferenceFilter < HTML::Pipeline::Filter
- def self.user_can_reference?(user, node, context)
+ def self.user_can_see_reference?(user, node, context)
if node.has_attribute?('data-project')
project_id = node.attr('data-project').to_i
return true if project_id == context[:project].try(:id)
@@ -24,6 +24,10 @@ module Banzai
end
end
+ def self.user_can_reference?(user, node, context)
+ true
+ end
+
def self.referenced_by(node)
raise NotImplementedError, "#{self} does not implement #{__method__}"
end
@@ -44,11 +48,11 @@ module Banzai
# Returns a String
def data_attribute(attributes = {})
attributes[:reference_filter] = self.class.name.demodulize
- attributes.map { |key, value| %Q(data-#{key.to_s.dasherize}="#{value}") }.join(" ")
+ attributes.map { |key, value| %Q(data-#{key.to_s.dasherize}="#{escape_once(value)}") }.join(" ")
end
def escape_once(html)
- ERB::Util.html_escape_once(html)
+ html.html_safe? ? html : ERB::Util.html_escape_once(html)
end
def ignore_parents
diff --git a/lib/banzai/filter/reference_gatherer_filter.rb b/lib/banzai/filter/reference_gatherer_filter.rb
index 855f238ac1e..12412ff7ea9 100644
--- a/lib/banzai/filter/reference_gatherer_filter.rb
+++ b/lib/banzai/filter/reference_gatherer_filter.rb
@@ -35,7 +35,9 @@ module Banzai
return if context[:reference_filter] && reference_filter != context[:reference_filter]
- return unless reference_filter.user_can_reference?(current_user, node, context)
+ return if author && !reference_filter.user_can_reference?(author, node, context)
+
+ return unless reference_filter.user_can_see_reference?(current_user, node, context)
references = reference_filter.referenced_by(node)
return unless references
@@ -57,6 +59,10 @@ module Banzai
def current_user
context[:current_user]
end
+
+ def author
+ context[:author]
+ end
end
end
end
diff --git a/lib/banzai/filter/table_of_contents_filter.rb b/lib/banzai/filter/table_of_contents_filter.rb
index 92d130074dc..9b3e67206d5 100644
--- a/lib/banzai/filter/table_of_contents_filter.rb
+++ b/lib/banzai/filter/table_of_contents_filter.rb
@@ -31,7 +31,7 @@ module Banzai
id = text.downcase
id.gsub!(PUNCTUATION_REGEXP, '') # remove punctuation
- id.gsub!(' ', '-') # replace spaces with dash
+ id.tr!(' ', '-') # replace spaces with dash
id.squeeze!('-') # replace multiple dashes with one
uniq = (headers[id] > 0) ? "-#{headers[id]}" : ''
diff --git a/lib/banzai/filter/user_reference_filter.rb b/lib/banzai/filter/user_reference_filter.rb
index 67c24faf991..964ab60f614 100644
--- a/lib/banzai/filter/user_reference_filter.rb
+++ b/lib/banzai/filter/user_reference_filter.rb
@@ -39,7 +39,7 @@ module Banzai
end
end
- def self.user_can_reference?(user, node, context)
+ def self.user_can_see_reference?(user, node, context)
if node.has_attribute?('data-group')
group = Group.find(node.attr('data-group')) rescue nil
Ability.abilities.allowed?(user, :read_group, group)
@@ -48,6 +48,18 @@ module Banzai
end
end
+ def self.user_can_reference?(user, node, context)
+ # Only team members can reference `@all`
+ if node.has_attribute?('data-project')
+ project = Project.find(node.attr('data-project')) rescue nil
+ return false unless project
+
+ user && project.team.member?(user)
+ else
+ super
+ end
+ end
+
def call
replace_text_nodes_matching(User.reference_pattern) do |content|
user_link_filter(content)
@@ -122,7 +134,7 @@ module Banzai
end
def link_tag(url, data, text)
- %(<a href="#{url}" #{data} class="#{link_class}">#{text}</a>)
+ %(<a href="#{url}" #{data} class="#{link_class}">#{escape_once(text)}</a>)
end
end
end
diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb
index 891c0fd7749..115ae914524 100644
--- a/lib/banzai/renderer.rb
+++ b/lib/banzai/renderer.rb
@@ -1,5 +1,7 @@
module Banzai
module Renderer
+ CACHE_ENABLED = false
+
# Convert a Markdown String into an HTML-safe String of HTML
#
# Note that while the returned HTML will have been sanitized of dangerous
@@ -18,7 +20,7 @@ module Banzai
cache_key = context.delete(:cache_key)
cache_key = full_cache_key(cache_key, context[:pipeline])
- if cache_key
+ if cache_key && CACHE_ENABLED
Rails.cache.fetch(cache_key) do
cacheless_render(text, context)
end
diff --git a/lib/ci/api/helpers.rb b/lib/ci/api/helpers.rb
index 443563c2e4a..1c91204e98c 100644
--- a/lib/ci/api/helpers.rb
+++ b/lib/ci/api/helpers.rb
@@ -19,7 +19,7 @@ module Ci
end
def runner_registration_token_valid?
- params[:token] == current_application_settings.ensure_runners_registration_token
+ params[:token] == current_application_settings.runners_registration_token
end
def update_runner_last_contact
diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb
index 87ac30b5ffe..459e3d6bcdb 100644
--- a/lib/gitlab/backend/shell.rb
+++ b/lib/gitlab/backend/shell.rb
@@ -2,7 +2,7 @@ module Gitlab
class Shell
class Error < StandardError; end
- class KeyAdder < Struct.new(:io)
+ KeyAdder = Struct.new(:io) do
def add_key(id, key)
key.gsub!(/[[:space:]]+/, ' ').strip!
io.puts("#{id}\t#{key}")
diff --git a/lib/gitlab/bitbucket_import/project_creator.rb b/lib/gitlab/bitbucket_import/project_creator.rb
index 35e34d033e0..03aac1a025a 100644
--- a/lib/gitlab/bitbucket_import/project_creator.rb
+++ b/lib/gitlab/bitbucket_import/project_creator.rb
@@ -11,7 +11,8 @@ module Gitlab
end
def execute
- project = ::Projects::CreateService.new(current_user,
+ project = ::Projects::CreateService.new(
+ current_user,
name: repo["name"],
path: repo["slug"],
description: repo["description"],
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index 46a4ef0e31f..7a86c09158e 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -38,7 +38,9 @@ module Gitlab
true
end
- use_db && ActiveRecord::Base.connection.active? && ActiveRecord::Base.connection.table_exists?('application_settings')
+ use_db && ActiveRecord::Base.connection.active? &&
+ !ActiveRecord::Migrator.needs_migration? &&
+ ActiveRecord::Base.connection.table_exists?('application_settings')
end
end
end
diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb
index 142058aa69d..79061cd0141 100644
--- a/lib/gitlab/diff/file.rb
+++ b/lib/gitlab/diff/file.rb
@@ -46,11 +46,11 @@ module Gitlab
end
def added_lines
- diff_lines.select(&:added?).size
+ diff_lines.count(&:added?)
end
def removed_lines
- diff_lines.select(&:removed?).size
+ diff_lines.count(&:removed?)
end
end
end
diff --git a/lib/gitlab/fogbugz_import/importer.rb b/lib/gitlab/fogbugz_import/importer.rb
index 496256700b8..403ebeec474 100644
--- a/lib/gitlab/fogbugz_import/importer.rb
+++ b/lib/gitlab/fogbugz_import/importer.rb
@@ -199,7 +199,7 @@ module Gitlab
s = s.gsub(/^#/, "\\#")
s = s.gsub(/^-/, "\\-")
s = s.gsub("`", "\\~")
- s = s.gsub("\r", "")
+ s = s.delete("\r")
s = s.gsub("\n", " \n")
s
end
diff --git a/lib/gitlab/fogbugz_import/project_creator.rb b/lib/gitlab/fogbugz_import/project_creator.rb
index 8b1b6f48ed5..e0163499e30 100644
--- a/lib/gitlab/fogbugz_import/project_creator.rb
+++ b/lib/gitlab/fogbugz_import/project_creator.rb
@@ -12,7 +12,8 @@ module Gitlab
end
def execute
- project = ::Projects::CreateService.new(current_user,
+ project = ::Projects::CreateService.new(
+ current_user,
name: repo.safe_name,
path: repo.path,
namespace: namespace,
diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb
index 0c350d7c675..f065cc5e9e9 100644
--- a/lib/gitlab/git.rb
+++ b/lib/gitlab/git.rb
@@ -20,6 +20,10 @@ module Gitlab
def blank_ref?(ref)
ref == BLANK_SHA
end
+
+ def version
+ Gitlab::VersionInfo.parse(Gitlab::Popen.popen(%W(#{Gitlab.config.git.bin_path} --version)).first)
+ end
end
end
end
diff --git a/lib/gitlab/gitlab_import/project_creator.rb b/lib/gitlab/gitlab_import/project_creator.rb
index d9452de6a50..7baaadb813c 100644
--- a/lib/gitlab/gitlab_import/project_creator.rb
+++ b/lib/gitlab/gitlab_import/project_creator.rb
@@ -11,7 +11,8 @@ module Gitlab
end
def execute
- project = ::Projects::CreateService.new(current_user,
+ project = ::Projects::CreateService.new(
+ current_user,
name: repo["name"],
path: repo["path"],
description: repo["description"],
diff --git a/lib/gitlab/gitorious_import/project_creator.rb b/lib/gitlab/gitorious_import/project_creator.rb
index cc9a91c91f4..8e22aa9286d 100644
--- a/lib/gitlab/gitorious_import/project_creator.rb
+++ b/lib/gitlab/gitorious_import/project_creator.rb
@@ -10,7 +10,8 @@ module Gitlab
end
def execute
- ::Projects::CreateService.new(current_user,
+ ::Projects::CreateService.new(
+ current_user,
name: repo.name,
path: repo.path,
description: repo.description,
diff --git a/lib/gitlab/google_code_import/importer.rb b/lib/gitlab/google_code_import/importer.rb
index 87fee28dc01..62da327931f 100644
--- a/lib/gitlab/google_code_import/importer.rb
+++ b/lib/gitlab/google_code_import/importer.rb
@@ -171,8 +171,6 @@ module Gitlab
when /\AMilestone:/
"#fee3ff"
- when *@closed_statuses.map { |s| nice_status_name(s) }
- "#cfcfcf"
when "Status: New"
"#428bca"
when "Status: Accepted"
@@ -199,6 +197,8 @@ module Gitlab
"#8e44ad"
when "Type: Other"
"#7f8c8d"
+ when *@closed_statuses.map { |s| nice_status_name(s) }
+ "#cfcfcf"
else
"#e2e2e2"
end
@@ -227,7 +227,7 @@ module Gitlab
s = s.gsub("`", "\\`")
# Carriage returns make me sad
- s = s.gsub("\r", "")
+ s = s.delete("\r")
# Markdown ignores single newlines, but we need them as <br />.
s = s.gsub("\n", " \n")
diff --git a/lib/gitlab/google_code_import/project_creator.rb b/lib/gitlab/google_code_import/project_creator.rb
index 1cb7d16aeb3..87821c23460 100644
--- a/lib/gitlab/google_code_import/project_creator.rb
+++ b/lib/gitlab/google_code_import/project_creator.rb
@@ -11,7 +11,8 @@ module Gitlab
end
def execute
- project = ::Projects::CreateService.new(current_user,
+ project = ::Projects::CreateService.new(
+ current_user,
name: repo.name,
path: repo.name,
description: repo.summary,
diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb
index 4be99dd88c2..aef08c97d1d 100644
--- a/lib/gitlab/ldap/user.rb
+++ b/lib/gitlab/ldap/user.rb
@@ -14,7 +14,7 @@ module Gitlab
# LDAP distinguished name is case-insensitive
identity = ::Identity.
where(provider: provider).
- where('lower(extern_uid) = ?', uid.mb_chars.downcase.to_s).last
+ iwhere(extern_uid: uid).last
identity && identity.user
end
end
@@ -31,7 +31,7 @@ module Gitlab
def find_by_uid_and_provider
self.class.find_by_uid_and_provider(
- auth_hash.uid.downcase, auth_hash.provider)
+ auth_hash.uid, auth_hash.provider)
end
def find_by_email
@@ -47,7 +47,7 @@ module Gitlab
# find_or_initialize_by doesn't update `gl_user.identities`, and isn't autosaved.
identity = gl_user.identities.find { |identity| identity.provider == auth_hash.provider }
identity ||= gl_user.identities.build(provider: auth_hash.provider)
-
+
# For a new user set extern_uid to the LDAP DN
# For an existing user with matching email but changed DN, update the DN.
# For an existing user with no change in DN, this line changes nothing.
diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb
new file mode 100644
index 00000000000..2d266ccfe9e
--- /dev/null
+++ b/lib/gitlab/metrics.rb
@@ -0,0 +1,104 @@
+module Gitlab
+ module Metrics
+ extend Gitlab::CurrentSettings
+
+ RAILS_ROOT = Rails.root.to_s
+ METRICS_ROOT = Rails.root.join('lib', 'gitlab', 'metrics').to_s
+ PATH_REGEX = /^#{RAILS_ROOT}\/?/
+
+ def self.pool_size
+ current_application_settings[:metrics_pool_size] || 16
+ end
+
+ def self.timeout
+ current_application_settings[:metrics_timeout] || 10
+ end
+
+ def self.enabled?
+ current_application_settings[:metrics_enabled] || false
+ end
+
+ def self.mri?
+ RUBY_ENGINE == 'ruby'
+ end
+
+ def self.method_call_threshold
+ # This is memoized since this method is called for every instrumented
+ # method. Loading data from an external cache on every method call slows
+ # things down too much.
+ @method_call_threshold ||=
+ (current_application_settings[:metrics_method_call_threshold] || 10)
+ end
+
+ def self.pool
+ @pool
+ end
+
+ def self.hostname
+ @hostname
+ end
+
+ # Returns a relative path and line number based on the last application call
+ # frame.
+ def self.last_relative_application_frame
+ frame = caller_locations.find do |l|
+ l.path.start_with?(RAILS_ROOT) && !l.path.start_with?(METRICS_ROOT)
+ end
+
+ if frame
+ return frame.path.sub(PATH_REGEX, ''), frame.lineno
+ else
+ return nil, nil
+ end
+ end
+
+ def self.submit_metrics(metrics)
+ prepared = prepare_metrics(metrics)
+
+ pool.with do |connection|
+ prepared.each do |metric|
+ begin
+ connection.write_points([metric])
+ rescue StandardError
+ end
+ end
+ end
+ end
+
+ def self.prepare_metrics(metrics)
+ metrics.map do |hash|
+ new_hash = hash.symbolize_keys
+
+ new_hash[:tags].each do |key, value|
+ if value.blank?
+ new_hash[:tags].delete(key)
+ else
+ new_hash[:tags][key] = escape_value(value)
+ end
+ end
+
+ new_hash
+ end
+ end
+
+ def self.escape_value(value)
+ value.to_s.gsub('=', '\\=')
+ end
+
+ @hostname = Socket.gethostname
+
+ # When enabled this should be set before being used as the usual pattern
+ # "@foo ||= bar" is _not_ thread-safe.
+ if enabled?
+ @pool = ConnectionPool.new(size: pool_size, timeout: timeout) do
+ host = current_application_settings[:metrics_host]
+ user = current_application_settings[:metrics_username]
+ pw = current_application_settings[:metrics_password]
+ port = current_application_settings[:metrics_port]
+
+ InfluxDB::Client.
+ new(udp: { host: host, port: port }, username: user, password: pw)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/delta.rb b/lib/gitlab/metrics/delta.rb
new file mode 100644
index 00000000000..bcf28eed84d
--- /dev/null
+++ b/lib/gitlab/metrics/delta.rb
@@ -0,0 +1,32 @@
+module Gitlab
+ module Metrics
+ # Class for calculating the difference between two numeric values.
+ #
+ # Every call to `compared_with` updates the internal value. This makes it
+ # possible to use a single Delta instance to calculate the delta over time
+ # of an ever increasing number.
+ #
+ # Example usage:
+ #
+ # delta = Delta.new(0)
+ #
+ # delta.compared_with(10) # => 10
+ # delta.compared_with(15) # => 5
+ # delta.compared_with(20) # => 5
+ class Delta
+ def initialize(value = 0)
+ @value = value
+ end
+
+ # new_value - The value to compare with as a Numeric.
+ #
+ # Returns a new Numeric (depending on the type of `new_value`).
+ def compared_with(new_value)
+ delta = new_value - @value
+ @value = new_value
+
+ delta
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/instrumentation.rb b/lib/gitlab/metrics/instrumentation.rb
new file mode 100644
index 00000000000..06fc2f25948
--- /dev/null
+++ b/lib/gitlab/metrics/instrumentation.rb
@@ -0,0 +1,146 @@
+module Gitlab
+ module Metrics
+ # Module for instrumenting methods.
+ #
+ # This module allows instrumenting of methods without having to actually
+ # alter the target code (e.g. by including modules).
+ #
+ # Example usage:
+ #
+ # Gitlab::Metrics::Instrumentation.instrument_method(User, :by_login)
+ module Instrumentation
+ SERIES = 'method_calls'
+
+ def self.configure
+ yield self
+ end
+
+ # Instruments a class method.
+ #
+ # mod - The module to instrument as a Module/Class.
+ # name - The name of the method to instrument.
+ def self.instrument_method(mod, name)
+ instrument(:class, mod, name)
+ end
+
+ # Instruments an instance method.
+ #
+ # mod - The module to instrument as a Module/Class.
+ # name - The name of the method to instrument.
+ def self.instrument_instance_method(mod, name)
+ instrument(:instance, mod, name)
+ end
+
+ # Recursively instruments all subclasses of the given root module.
+ #
+ # This can be used to for example instrument all ActiveRecord models (as
+ # these all inherit from ActiveRecord::Base).
+ #
+ # This method can optionally take a block to pass to `instrument_methods`
+ # and `instrument_instance_methods`.
+ #
+ # root - The root module for which to instrument subclasses. The root
+ # module itself is not instrumented.
+ def self.instrument_class_hierarchy(root, &block)
+ visit = root.subclasses
+
+ until visit.empty?
+ klass = visit.pop
+
+ instrument_methods(klass, &block)
+ instrument_instance_methods(klass, &block)
+
+ klass.subclasses.each { |c| visit << c }
+ end
+ end
+
+ # Instruments all public methods of a module.
+ #
+ # This method optionally takes a block that can be used to determine if a
+ # method should be instrumented or not. The block is passed the receiving
+ # module and an UnboundMethod. If the block returns a non truthy value the
+ # method is not instrumented.
+ #
+ # mod - The module to instrument.
+ def self.instrument_methods(mod)
+ mod.public_methods(false).each do |name|
+ method = mod.method(name)
+
+ if method.owner == mod.singleton_class
+ if !block_given? || block_given? && yield(mod, method)
+ instrument_method(mod, name)
+ end
+ end
+ end
+ end
+
+ # Instruments all public instance methods of a module.
+ #
+ # See `instrument_methods` for more information.
+ #
+ # mod - The module to instrument.
+ def self.instrument_instance_methods(mod)
+ mod.public_instance_methods(false).each do |name|
+ method = mod.instance_method(name)
+
+ if method.owner == mod
+ if !block_given? || block_given? && yield(mod, method)
+ instrument_instance_method(mod, name)
+ end
+ end
+ end
+ end
+
+ # Instruments a method.
+ #
+ # type - The type (:class or :instance) of method to instrument.
+ # mod - The module containing the method.
+ # name - The name of the method to instrument.
+ def self.instrument(type, mod, name)
+ return unless Metrics.enabled?
+
+ name = name.to_sym
+ alias_name = :"_original_#{name}"
+ target = type == :instance ? mod : mod.singleton_class
+
+ if type == :instance
+ target = mod
+ label = "#{mod.name}##{name}"
+ else
+ target = mod.singleton_class
+ label = "#{mod.name}.#{name}"
+ end
+
+ target.class_eval <<-EOF, __FILE__, __LINE__ + 1
+ alias_method #{alias_name.inspect}, #{name.inspect}
+
+ def #{name}(*args, &block)
+ trans = Gitlab::Metrics::Instrumentation.transaction
+
+ if trans
+ start = Time.now
+ retval = __send__(#{alias_name.inspect}, *args, &block)
+ duration = (Time.now - start) * 1000.0
+
+ if duration >= Gitlab::Metrics.method_call_threshold
+ trans.add_metric(Gitlab::Metrics::Instrumentation::SERIES,
+ { duration: duration },
+ method: #{label.inspect})
+ end
+
+ retval
+ else
+ __send__(#{alias_name.inspect}, *args, &block)
+ end
+ end
+ EOF
+ end
+
+ # Small layer of indirection to make it easier to stub out the current
+ # transaction.
+ def self.transaction
+ Transaction.current
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/metric.rb b/lib/gitlab/metrics/metric.rb
new file mode 100644
index 00000000000..753008df99a
--- /dev/null
+++ b/lib/gitlab/metrics/metric.rb
@@ -0,0 +1,31 @@
+module Gitlab
+ module Metrics
+ # Class for storing details of a single metric (label, value, etc).
+ class Metric
+ attr_reader :series, :values, :tags, :created_at
+
+ # 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
+ end
+
+ # Returns a Hash in a format that can be directly written to InfluxDB.
+ def to_hash
+ {
+ series: @series,
+ tags: @tags.merge(
+ hostname: Metrics.hostname,
+ process_type: Sidekiq.server? ? 'sidekiq' : 'rails'
+ ),
+ values: @values,
+ timestamp: @created_at.to_i * 1_000_000_000
+ }
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/obfuscated_sql.rb b/lib/gitlab/metrics/obfuscated_sql.rb
new file mode 100644
index 00000000000..fe97d7a0534
--- /dev/null
+++ b/lib/gitlab/metrics/obfuscated_sql.rb
@@ -0,0 +1,47 @@
+module Gitlab
+ module Metrics
+ # Class for producing SQL queries with sensitive data stripped out.
+ class ObfuscatedSQL
+ REPLACEMENT = /
+ \d+(\.\d+)? # integers, floats
+ | '.+?' # single quoted strings
+ | \/.+?(?<!\\)\/ # regexps (including escaped slashes)
+ /x
+
+ MYSQL_REPLACEMENTS = /
+ ".+?" # double quoted strings
+ /x
+
+ # Regex to replace consecutive placeholders with a single one indicating
+ # the length. This can be useful when a "IN" statement uses thousands of
+ # IDs (storing this would just be a waste of space).
+ CONSECUTIVE = /(\?(\s*,\s*)?){2,}/
+
+ # sql - The raw SQL query as a String.
+ def initialize(sql)
+ @sql = sql
+ end
+
+ # Returns a new, obfuscated SQL query.
+ def to_s
+ regex = REPLACEMENT
+
+ if Gitlab::Database.mysql?
+ regex = Regexp.union(regex, MYSQL_REPLACEMENTS)
+ end
+
+ sql = @sql.gsub(regex, '?').gsub(CONSECUTIVE) do |match|
+ "#{match.count(',') + 1} values"
+ end
+
+ # InfluxDB escapes double quotes upon output, so lets get rid of them
+ # whenever we can.
+ if Gitlab::Database.postgresql?
+ sql = sql.delete('"')
+ end
+
+ sql.tr("\n", ' ')
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/rack_middleware.rb b/lib/gitlab/metrics/rack_middleware.rb
new file mode 100644
index 00000000000..5c0587c4c51
--- /dev/null
+++ b/lib/gitlab/metrics/rack_middleware.rb
@@ -0,0 +1,49 @@
+module Gitlab
+ module Metrics
+ # Rack middleware for tracking Rails requests.
+ class RackMiddleware
+ CONTROLLER_KEY = 'action_controller.instance'
+
+ def initialize(app)
+ @app = app
+ end
+
+ # env - A Hash containing Rack environment details.
+ def call(env)
+ trans = transaction_from_env(env)
+ retval = nil
+
+ begin
+ retval = trans.run { @app.call(env) }
+
+ # Even in the event of an error we want to submit any metrics we
+ # might've gathered up to this point.
+ ensure
+ if env[CONTROLLER_KEY]
+ tag_controller(trans, env)
+ end
+
+ trans.finish
+ end
+
+ retval
+ end
+
+ def transaction_from_env(env)
+ trans = Transaction.new
+
+ trans.add_tag(:request_method, env['REQUEST_METHOD'])
+ trans.add_tag(:request_uri, env['REQUEST_URI'])
+
+ trans
+ end
+
+ def tag_controller(trans, env)
+ controller = env[CONTROLLER_KEY]
+ label = "#{controller.class.name}##{controller.action_name}"
+
+ trans.add_tag(:action, label)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/sampler.rb b/lib/gitlab/metrics/sampler.rb
new file mode 100644
index 00000000000..998578e1c0a
--- /dev/null
+++ b/lib/gitlab/metrics/sampler.rb
@@ -0,0 +1,98 @@
+module Gitlab
+ module Metrics
+ # Class that sends certain metrics to InfluxDB at a specific interval.
+ #
+ # This class is used to gather statistics that can't be directly associated
+ # with a transaction such as system memory usage, garbage collection
+ # statistics, etc.
+ class Sampler
+ # interval - The sampling interval in seconds.
+ def initialize(interval = 15)
+ @interval = interval
+ @metrics = []
+
+ @last_minor_gc = Delta.new(GC.stat[:minor_gc_count])
+ @last_major_gc = Delta.new(GC.stat[:major_gc_count])
+
+ if Gitlab::Metrics.mri?
+ require 'allocations'
+
+ Allocations.start
+ end
+ end
+
+ def start
+ Thread.new do
+ Thread.current.abort_on_exception = true
+
+ loop do
+ sleep(@interval)
+
+ sample
+ end
+ end
+ end
+
+ def sample
+ sample_memory_usage
+ sample_file_descriptors
+ sample_objects
+ sample_gc
+
+ flush
+ ensure
+ GC::Profiler.clear
+ @metrics.clear
+ end
+
+ def flush
+ Metrics.submit_metrics(@metrics.map(&:to_hash))
+ end
+
+ def sample_memory_usage
+ @metrics << Metric.new('memory_usage', value: System.memory_usage)
+ end
+
+ def sample_file_descriptors
+ @metrics << Metric.
+ new('file_descriptors', value: System.file_descriptor_count)
+ end
+
+ if Metrics.mri?
+ def sample_objects
+ sample = Allocations.to_hash
+ counts = sample.each_with_object({}) do |(klass, count), hash|
+ hash[klass.name] = count
+ end
+
+ # Symbols aren't allocated so we'll need to add those manually.
+ counts['Symbol'] = Symbol.all_symbols.length
+
+ counts.each do |name, count|
+ @metrics << Metric.new('object_counts', { count: count }, type: name)
+ end
+ end
+ else
+ def sample_objects
+ end
+ end
+
+ def sample_gc
+ time = GC::Profiler.total_time * 1000.0
+ stats = GC.stat.merge(total_time: time)
+
+ # We want the difference of GC runs compared to the last sample, not the
+ # total amount since the process started.
+ stats[:minor_gc_count] =
+ @last_minor_gc.compared_with(stats[:minor_gc_count])
+
+ stats[:major_gc_count] =
+ @last_major_gc.compared_with(stats[:major_gc_count])
+
+ stats[:count] = stats[:minor_gc_count] + stats[:major_gc_count]
+
+ @metrics << Metric.new('gc_statistics', stats)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/sidekiq_middleware.rb b/lib/gitlab/metrics/sidekiq_middleware.rb
new file mode 100644
index 00000000000..ad441decfa2
--- /dev/null
+++ b/lib/gitlab/metrics/sidekiq_middleware.rb
@@ -0,0 +1,23 @@
+module Gitlab
+ module Metrics
+ # Sidekiq middleware for tracking jobs.
+ #
+ # This middleware is intended to be used as a server-side middleware.
+ class SidekiqMiddleware
+ def call(worker, message, queue)
+ trans = Transaction.new
+
+ begin
+ trans.run { yield }
+ ensure
+ tag_worker(trans, worker)
+ trans.finish
+ end
+ end
+
+ def tag_worker(trans, worker)
+ trans.add_tag(:action, "#{worker.class.name}#perform")
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/subscribers/action_view.rb b/lib/gitlab/metrics/subscribers/action_view.rb
new file mode 100644
index 00000000000..7e0dcf99d92
--- /dev/null
+++ b/lib/gitlab/metrics/subscribers/action_view.rb
@@ -0,0 +1,53 @@
+module Gitlab
+ module Metrics
+ module Subscribers
+ # Class for tracking the rendering timings of views.
+ class ActionView < ActiveSupport::Subscriber
+ attach_to :action_view
+
+ SERIES = 'views'
+
+ def render_template(event)
+ track(event) if current_transaction
+ end
+
+ alias_method :render_view, :render_template
+
+ private
+
+ def track(event)
+ values = values_for(event)
+ tags = tags_for(event)
+
+ current_transaction.add_metric(SERIES, values, tags)
+ end
+
+ def relative_path(path)
+ path.gsub(/^#{Rails.root.to_s}\/?/, '')
+ end
+
+ def values_for(event)
+ { duration: event.duration }
+ end
+
+ def tags_for(event)
+ path = relative_path(event.payload[:identifier])
+ tags = { view: path }
+
+ file, line = Metrics.last_relative_application_frame
+
+ if file and line
+ tags[:file] = file
+ tags[:line] = line
+ end
+
+ tags
+ end
+
+ def current_transaction
+ Transaction.current
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/subscribers/active_record.rb b/lib/gitlab/metrics/subscribers/active_record.rb
new file mode 100644
index 00000000000..d947c128ce2
--- /dev/null
+++ b/lib/gitlab/metrics/subscribers/active_record.rb
@@ -0,0 +1,48 @@
+module Gitlab
+ module Metrics
+ module Subscribers
+ # Class for tracking raw SQL queries.
+ #
+ # Queries are obfuscated before being logged to ensure no private data is
+ # exposed via InfluxDB/Grafana.
+ class ActiveRecord < ActiveSupport::Subscriber
+ attach_to :active_record
+
+ SERIES = 'sql_queries'
+
+ def sql(event)
+ return unless current_transaction
+
+ values = values_for(event)
+ tags = tags_for(event)
+
+ current_transaction.add_metric(SERIES, values, tags)
+ end
+
+ private
+
+ def values_for(event)
+ { duration: event.duration }
+ end
+
+ def tags_for(event)
+ sql = ObfuscatedSQL.new(event.payload[:sql]).to_s
+ tags = { sql: sql }
+
+ file, line = Metrics.last_relative_application_frame
+
+ if file and line
+ tags[:file] = file
+ tags[:line] = line
+ end
+
+ tags
+ end
+
+ def current_transaction
+ Transaction.current
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/system.rb b/lib/gitlab/metrics/system.rb
new file mode 100644
index 00000000000..83371265278
--- /dev/null
+++ b/lib/gitlab/metrics/system.rb
@@ -0,0 +1,35 @@
+module Gitlab
+ module Metrics
+ # Module for gathering system/process statistics such as the memory usage.
+ #
+ # This module relies on the /proc filesystem being available. If /proc is
+ # not available the methods of this module will be stubbed.
+ module System
+ if File.exist?('/proc')
+ # Returns the current process' memory usage in bytes.
+ def self.memory_usage
+ mem = 0
+ match = File.read('/proc/self/status').match(/VmRSS:\s+(\d+)/)
+
+ if match and match[1]
+ mem = match[1].to_f * 1024
+ end
+
+ mem
+ end
+
+ def self.file_descriptor_count
+ Dir.glob('/proc/self/fd/*').length
+ end
+ else
+ def self.memory_usage
+ 0.0
+ end
+
+ def self.file_descriptor_count
+ 0
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/transaction.rb b/lib/gitlab/metrics/transaction.rb
new file mode 100644
index 00000000000..a61dbd989e7
--- /dev/null
+++ b/lib/gitlab/metrics/transaction.rb
@@ -0,0 +1,66 @@
+module Gitlab
+ module Metrics
+ # Class for storing metrics information of a single transaction.
+ class Transaction
+ THREAD_KEY = :_gitlab_metrics_transaction
+
+ SERIES = 'transactions'
+
+ attr_reader :uuid, :tags
+
+ def self.current
+ Thread.current[THREAD_KEY]
+ end
+
+ # name - The name of this transaction as a String.
+ def initialize
+ @metrics = []
+ @uuid = SecureRandom.uuid
+
+ @started_at = nil
+ @finished_at = nil
+
+ @tags = {}
+ end
+
+ def duration
+ @finished_at ? (@finished_at - @started_at) * 1000.0 : 0.0
+ end
+
+ def run
+ Thread.current[THREAD_KEY] = self
+
+ @started_at = Time.now
+
+ yield
+ ensure
+ @finished_at = Time.now
+
+ Thread.current[THREAD_KEY] = nil
+ end
+
+ def add_metric(series, values, tags = {})
+ tags = tags.merge(transaction_id: @uuid)
+
+ @metrics << Metric.new(series, values, tags)
+ end
+
+ def add_tag(key, value)
+ @tags[key] = value
+ end
+
+ def finish
+ track_self
+ submit
+ end
+
+ def track_self
+ add_metric(SERIES, { duration: duration }, @tags)
+ end
+
+ def submit
+ Metrics.submit_metrics(@metrics.map(&:to_hash))
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/o_auth/session.rb b/lib/gitlab/o_auth/session.rb
new file mode 100644
index 00000000000..f33bfd0bd0e
--- /dev/null
+++ b/lib/gitlab/o_auth/session.rb
@@ -0,0 +1,17 @@
+module Gitlab
+ module OAuth
+ module Session
+ def self.create(provider, ticket)
+ Rails.cache.write("gitlab:#{provider}:#{ticket}", ticket, expires_in: Gitlab.config.omniauth.cas3.session_duration)
+ end
+
+ def self.destroy(provider, ticket)
+ Rails.cache.delete("gitlab:#{provider}:#{ticket}")
+ end
+
+ def self.valid?(provider, ticket)
+ Rails.cache.read("gitlab:#{provider}:#{ticket}").present?
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb
index 17ce4d4b174..f1a362f5303 100644
--- a/lib/gitlab/o_auth/user.rb
+++ b/lib/gitlab/o_auth/user.rb
@@ -64,7 +64,7 @@ module Gitlab
# If a corresponding person exists with same uid in a LDAP server,
# set up a Gitlab user with dual LDAP and Omniauth identities.
- if user = Gitlab::LDAP::User.find_by_uid_and_provider(ldap_person.dn.downcase, ldap_person.provider)
+ if user = Gitlab::LDAP::User.find_by_uid_and_provider(ldap_person.dn, ldap_person.provider)
# Case when a LDAP user already exists in Gitlab. Add the Omniauth identity to existing account.
user.identities.build(extern_uid: auth_hash.uid, provider: auth_hash.provider)
else
diff --git a/lib/gitlab/recaptcha.rb b/lib/gitlab/recaptcha.rb
new file mode 100644
index 00000000000..70e7f25d518
--- /dev/null
+++ b/lib/gitlab/recaptcha.rb
@@ -0,0 +1,14 @@
+module Gitlab
+ module Recaptcha
+ def self.load_configurations!
+ if current_application_settings.recaptcha_enabled
+ ::Recaptcha.configure do |config|
+ config.public_key = current_application_settings.recaptcha_site_key
+ config.private_key = current_application_settings.recaptcha_private_key
+ end
+
+ true
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb
index 42f7c26f3c4..be795649e59 100644
--- a/lib/gitlab/reference_extractor.rb
+++ b/lib/gitlab/reference_extractor.rb
@@ -3,11 +3,12 @@ require 'banzai'
module Gitlab
# Extract possible GFM references from an arbitrary String for further processing.
class ReferenceExtractor < Banzai::ReferenceExtractor
- attr_accessor :project, :current_user
+ attr_accessor :project, :current_user, :author
- def initialize(project, current_user = nil)
+ def initialize(project, current_user = nil, author = nil)
@project = project
@current_user = current_user
+ @author = author
@references = {}
@@ -18,10 +19,24 @@ module Gitlab
super(text, context.merge(project: project))
end
- %i(user label issue merge_request snippet commit commit_range).each do |type|
+ %i(user label merge_request snippet commit commit_range).each do |type|
define_method("#{type}s") do
- @references[type] ||= references(type, project: project, current_user: current_user)
+ @references[type] ||= references(type, reference_context)
end
end
+
+ def issues
+ if project && project.jira_tracker?
+ @references[:external_issue] ||= references(:external_issue, reference_context)
+ else
+ @references[:issue] ||= references(:issue, reference_context)
+ end
+ end
+
+ private
+
+ def reference_context
+ { project: project, current_user: current_user, author: author }
+ end
end
end
diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb
index 335dc44be19..3160a3c7582 100644
--- a/lib/gitlab/visibility_level.rb
+++ b/lib/gitlab/visibility_level.rb
@@ -51,6 +51,15 @@ module Gitlab
def allowed_fork_levels(origin_level)
[PRIVATE, INTERNAL, PUBLIC].select{ |level| level <= origin_level }
end
+
+ def level_name(level)
+ level_name = 'Unknown'
+ options.each do |name, lvl|
+ level_name = name if lvl == level.to_i
+ end
+
+ level_name
+ end
end
def private?
diff --git a/lib/rouge/formatters/html_gitlab.rb b/lib/rouge/formatters/html_gitlab.rb
index 6762ca47c32..8c309efc7b8 100644
--- a/lib/rouge/formatters/html_gitlab.rb
+++ b/lib/rouge/formatters/html_gitlab.rb
@@ -39,7 +39,7 @@ module Rouge
lineanchorsid: 'L',
anchorlinenos: false,
inline_theme: nil
- )
+ )
@nowrap = nowrap
@cssclass = cssclass
@linenos = linenos
diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab
index 43fda6fa92e..c5f07c8b508 100755
--- a/lib/support/init.d/gitlab
+++ b/lib/support/init.d/gitlab
@@ -33,12 +33,13 @@ app_user="git"
app_root="/home/$app_user/gitlab"
pid_path="$app_root/tmp/pids"
socket_path="$app_root/tmp/sockets"
+rails_socket="$socket_path/gitlab.socket"
web_server_pid_path="$pid_path/unicorn.pid"
sidekiq_pid_path="$pid_path/sidekiq.pid"
mail_room_enabled=false
mail_room_pid_path="$pid_path/mail_room.pid"
gitlab_workhorse_pid_path="$pid_path/gitlab-workhorse.pid"
-gitlab_workhorse_options="-listenUmask 0 -listenNetwork unix -listenAddr $socket_path/gitlab-workhorse.socket -authBackend http://127.0.0.1:8080"
+gitlab_workhorse_options="-listenUmask 0 -listenNetwork unix -listenAddr $socket_path/gitlab-workhorse.socket -authBackend http://127.0.0.1:8080 -authSocket $rails_socket -documentRoot $app_root/public"
gitlab_workhorse_log="$app_root/log/gitlab-workhorse.log"
shell_path="/bin/bash"
@@ -91,7 +92,7 @@ check_pids(){
## Called when we have started the two processes and are waiting for their pid files.
wait_for_pids(){
- # We are sleeping a bit here mostly because sidekiq is slow at writing it's pid
+ # We are sleeping a bit here mostly because sidekiq is slow at writing its pid
i=0;
while [ ! -f $web_server_pid_path ] || [ ! -f $sidekiq_pid_path ] || [ ! -f $gitlab_workhorse_pid_path ] || { [ "$mail_room_enabled" = true ] && [ ! -f $mail_room_pid_path ]; }; do
sleep 0.1;
@@ -107,7 +108,7 @@ wait_for_pids(){
}
# We use the pids in so many parts of the script it makes sense to always check them.
-# Only after start() is run should the pids change. Sidekiq sets it's own pid.
+# Only after start() is run should the pids change. Sidekiq sets its own pid.
check_pids
@@ -289,7 +290,7 @@ stop_gitlab() {
sleep 1
# Cleaning up unused pids
rm "$web_server_pid_path" 2>/dev/null
- # rm "$sidekiq_pid_path" 2>/dev/null # Sidekiq seems to be cleaning up it's own pid.
+ # rm "$sidekiq_pid_path" 2>/dev/null # Sidekiq seems to be cleaning up its own pid.
rm -f "$gitlab_workhorse_pid_path"
if [ "$mail_room_enabled" = true ]; then
rm "$mail_room_pid_path" 2>/dev/null
@@ -298,7 +299,7 @@ stop_gitlab() {
print_status
}
-## Prints the status of GitLab and it's components.
+## Prints the status of GitLab and its components.
print_status() {
check_status
if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && [ "$gitlab_workhorse_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; }; then
@@ -332,7 +333,7 @@ print_status() {
fi
}
-## Tells unicorn to reload it's config and Sidekiq to restart
+## Tells unicorn to reload its config and Sidekiq to restart
reload_gitlab(){
exit_if_not_running
if [ "$wpid" = "0" ];then
diff --git a/lib/support/init.d/gitlab.default.example b/lib/support/init.d/gitlab.default.example
index 79ae8e0ae55..1937ca582b0 100755
--- a/lib/support/init.d/gitlab.default.example
+++ b/lib/support/init.d/gitlab.default.example
@@ -9,11 +9,11 @@ RAILS_ENV="production"
# The default is "git".
app_user="git"
-# app_root defines the folder in which gitlab and it's components are installed.
+# app_root defines the folder in which gitlab and its components are installed.
# The default is "/home/$app_user/gitlab"
app_root="/home/$app_user/gitlab"
-# pid_path defines a folder in which the gitlab and it's components place their pids.
+# pid_path defines a folder in which the gitlab and its components place their pids.
# This variable is also used below to define the relevant pids for the gitlab components.
# The default is "$app_root/tmp/pids"
pid_path="$app_root/tmp/pids"
@@ -36,7 +36,7 @@ gitlab_workhorse_pid_path="$pid_path/gitlab-workhorse.pid"
# '-listenNetwork tcp -listenAddr localhost:8181'.
# The -authBackend setting tells gitlab-workhorse where it can reach
# Unicorn.
-gitlab_workhorse_options="-listenUmask 0 -listenNetwork unix -listenAddr $socket_path/gitlab-workhorse.socket -authBackend http://127.0.0.1:8080"
+gitlab_workhorse_options="-listenUmask 0 -listenNetwork unix -listenAddr $socket_path/gitlab-workhorse.socket -authBackend http://127.0.0.1:8080 -authSocket $socket_path/gitlab.socket -documentRoot $app_root/public"
gitlab_workhorse_log="$app_root/log/gitlab-workhorse.log"
# mail_room_enabled specifies whether mail_room, which is used to process incoming email, is enabled.
diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab
index 2a79fbdcf93..fc5475c4eef 100644
--- a/lib/support/nginx/gitlab
+++ b/lib/support/nginx/gitlab
@@ -10,34 +10,12 @@
## If you change this file in a Merge Request, please also create
## a Merge Request on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests
##
-##################################
-## CHUNKED TRANSFER ##
-##################################
-##
-## It is a known issue that Git-over-HTTP requires chunked transfer encoding [0]
-## which is not supported by Nginx < 1.3.9 [1]. As a result, pushing a large object
-## with Git (i.e. a single large file) can lead to a 411 error. In theory you can get
-## around this by tweaking this configuration file and either:
-## - installing an old version of Nginx with the chunkin module [2] compiled in, or
-## - using a newer version of Nginx.
-##
-## At the time of writing we do not know if either of these theoretical solutions works.
-## As a workaround users can use Git over SSH to push large files.
-##
-## [0] https://git.kernel.org/cgit/git/git.git/tree/Documentation/technical/http-protocol.txt#n99
-## [1] https://github.com/agentzh/chunkin-nginx-module#status
-## [2] https://github.com/agentzh/chunkin-nginx-module
-##
###################################
## configuration ##
###################################
##
## See installation.md#using-https for additional HTTPS configuration details.
-upstream gitlab {
- server unix:/home/git/gitlab/tmp/sockets/gitlab.socket fail_timeout=0;
-}
-
upstream gitlab-workhorse {
server unix:/home/git/gitlab/tmp/sockets/gitlab-workhorse.socket fail_timeout=0;
}
@@ -54,10 +32,6 @@ server {
server_tokens off; ## Don't show the nginx version number, a security best practice
root /home/git/gitlab/public;
- ## Increase this if you want to upload large attachments
- ## Or if you want to accept large git objects over http
- client_max_body_size 20m;
-
## See app/controllers/application_controller.rb for headers set
## Individual nginx logs for this GitLab vhost
@@ -65,103 +39,8 @@ server {
error_log /var/log/nginx/gitlab_error.log;
location / {
- ## Serve static files from defined root folder.
- ## @gitlab is a named location for the upstream fallback, see below.
- try_files $uri /index.html $uri.html @gitlab;
- }
-
- ## We route uploads through GitLab to prevent XSS and enforce access control.
- location /uploads/ {
- ## If you use HTTPS make sure you disable gzip compression
- ## to be safe against BREACH attack.
- # gzip off;
-
- ## https://github.com/gitlabhq/gitlabhq/issues/694
- ## Some requests take more than 30 seconds.
- proxy_read_timeout 300;
- proxy_connect_timeout 300;
- proxy_redirect off;
-
- proxy_set_header Host $http_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;
- proxy_set_header X-Frame-Options SAMEORIGIN;
-
- proxy_pass http://gitlab;
- }
-
- ## If a file, which is not found in the root folder is requested,
- ## then the proxy passes the request to the upsteam (gitlab unicorn).
- location @gitlab {
- ## If you use HTTPS make sure you disable gzip compression
- ## to be safe against BREACH attack.
- # gzip off;
-
- ## https://github.com/gitlabhq/gitlabhq/issues/694
- ## Some requests take more than 30 seconds.
- proxy_read_timeout 300;
- proxy_connect_timeout 300;
- proxy_redirect off;
-
- proxy_set_header Host $http_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;
- proxy_set_header X-Frame-Options SAMEORIGIN;
-
- proxy_pass http://gitlab;
- }
-
- location ~ ^/[\w\.-]+/[\w\.-]+/gitlab-lfs/objects {
- client_max_body_size 0;
- # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
- error_page 418 = @gitlab-workhorse;
- return 418;
- }
-
- location ~ ^/[\w\.-]+/[\w\.-]+/(info/refs|git-upload-pack|git-receive-pack)$ {
- client_max_body_size 0;
- # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
- error_page 418 = @gitlab-workhorse;
- return 418;
- }
-
- location ~ ^/[\w\.-]+/[\w\.-]+/repository/archive {
- client_max_body_size 0;
- # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
- error_page 418 = @gitlab-workhorse;
- return 418;
- }
-
- location ~ ^/api/v3/projects/.*/repository/archive {
- client_max_body_size 0;
- # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
- error_page 418 = @gitlab-workhorse;
- return 418;
- }
-
- # Build artifacts should be submitted to this location
- location ~ ^/[\w\.-]+/[\w\.-]+/builds/download {
client_max_body_size 0;
- # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
- error_page 418 = @gitlab-workhorse;
- return 418;
- }
-
- # Build artifacts should be submitted to this location
- location ~ /ci/api/v1/builds/[0-9]+/artifacts {
- client_max_body_size 0;
- # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
- error_page 418 = @gitlab-workhorse;
- return 418;
- }
-
- location @gitlab-workhorse {
- client_max_body_size 0;
- ## If you use HTTPS make sure you disable gzip compression
- ## to be safe against BREACH attack.
- # gzip off;
+ gzip off;
## https://github.com/gitlabhq/gitlabhq/issues/694
## Some requests take more than 30 seconds.
@@ -169,14 +48,7 @@ server {
proxy_connect_timeout 300;
proxy_redirect off;
- # Do not buffer Git HTTP responses
- proxy_buffering off;
-
- # The following settings only work with NGINX 1.7.11 or newer
- #
- # # Pass chunked request bodies to gitlab-workhorse as-is
- # proxy_request_buffering off;
- # proxy_http_version 1.1;
+ proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
@@ -185,18 +57,4 @@ server {
proxy_pass http://gitlab-workhorse;
}
-
- ## Enable gzip compression as per rails guide:
- ## http://guides.rubyonrails.org/asset_pipeline.html#gzip-compression
- ## WARNING: If you are using relative urls remove the block below
- ## See config/application.rb under "Relative url support" for the list of
- ## other files that need to be changed for relative url support
- location ~ ^/(assets)/ {
- root /home/git/gitlab/public;
- gzip_static on; # to serve pre-gzipped version
- expires max;
- add_header Cache-Control public;
- }
-
- error_page 502 /502.html;
}
diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl
index 79fe1474821..1e5f85413ec 100644
--- a/lib/support/nginx/gitlab-ssl
+++ b/lib/support/nginx/gitlab-ssl
@@ -14,34 +14,12 @@
## If you change this file in a Merge Request, please also create
## a Merge Request on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests
##
-##################################
-## CHUNKED TRANSFER ##
-##################################
-##
-## It is a known issue that Git-over-HTTP requires chunked transfer encoding [0]
-## which is not supported by Nginx < 1.3.9 [1]. As a result, pushing a large object
-## with Git (i.e. a single large file) can lead to a 411 error. In theory you can get
-## around this by tweaking this configuration file and either:
-## - installing an old version of Nginx with the chunkin module [2] compiled in, or
-## - using a newer version of Nginx.
-##
-## At the time of writing we do not know if either of these theoretical solutions works.
-## As a workaround users can use Git over SSH to push large files.
-##
-## [0] https://git.kernel.org/cgit/git/git.git/tree/Documentation/technical/http-protocol.txt#n99
-## [1] https://github.com/agentzh/chunkin-nginx-module#status
-## [2] https://github.com/agentzh/chunkin-nginx-module
-##
###################################
## configuration ##
###################################
##
## See installation.md#using-https for additional HTTPS configuration details.
-upstream gitlab {
- server unix:/home/git/gitlab/tmp/sockets/gitlab.socket fail_timeout=0;
-}
-
upstream gitlab-workhorse {
server unix:/home/git/gitlab/tmp/sockets/gitlab-workhorse.socket fail_timeout=0;
}
@@ -61,7 +39,6 @@ server {
error_log /var/log/nginx/gitlab_error.log;
}
-
## HTTPS host
server {
listen 0.0.0.0:443 ssl;
@@ -70,10 +47,6 @@ server {
server_tokens off; ## Don't show the nginx version number, a security best practice
root /home/git/gitlab/public;
- ## Increase this if you want to upload large attachments
- ## Or if you want to accept large git objects over http
- client_max_body_size 20m;
-
## Strong SSL Security
## https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html & https://cipherli.st/
ssl on;
@@ -110,104 +83,7 @@ server {
error_log /var/log/nginx/gitlab_error.log;
location / {
- ## Serve static files from defined root folder.
- ## @gitlab is a named location for the upstream fallback, see below.
- try_files $uri /index.html $uri.html @gitlab;
- }
-
- ## We route uploads through GitLab to prevent XSS and enforce access control.
- location /uploads/ {
- ## If you use HTTPS make sure you disable gzip compression
- ## to be safe against BREACH attack.
- gzip off;
-
- ## https://github.com/gitlabhq/gitlabhq/issues/694
- ## Some requests take more than 30 seconds.
- proxy_read_timeout 300;
- proxy_connect_timeout 300;
- proxy_redirect off;
-
- proxy_set_header Host $http_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;
- proxy_set_header X-Forwarded-Proto $scheme;
- proxy_set_header X-Frame-Options SAMEORIGIN;
-
- proxy_pass http://gitlab;
- }
-
- ## If a file, which is not found in the root folder is requested,
- ## then the proxy passes the request to the upsteam (gitlab unicorn).
- location @gitlab {
- ## If you use HTTPS make sure you disable gzip compression
- ## to be safe against BREACH attack.
- gzip off;
-
- ## https://github.com/gitlabhq/gitlabhq/issues/694
- ## Some requests take more than 30 seconds.
- proxy_read_timeout 300;
- proxy_connect_timeout 300;
- proxy_redirect off;
-
- proxy_set_header Host $http_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;
- proxy_set_header X-Forwarded-Proto $scheme;
- proxy_set_header X-Frame-Options SAMEORIGIN;
-
- proxy_pass http://gitlab;
- }
-
- location ~ ^/[\w\.-]+/[\w\.-]+/gitlab-lfs/objects {
- client_max_body_size 0;
- # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
- error_page 418 = @gitlab-workhorse;
- return 418;
- }
-
- location ~ ^/[\w\.-]+/[\w\.-]+/(info/refs|git-upload-pack|git-receive-pack)$ {
- client_max_body_size 0;
- # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
- error_page 418 = @gitlab-workhorse;
- return 418;
- }
-
- location ~ ^/[\w\.-]+/[\w\.-]+/repository/archive {
client_max_body_size 0;
- # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
- error_page 418 = @gitlab-workhorse;
- return 418;
- }
-
- location ~ ^/api/v3/projects/.*/repository/archive {
- client_max_body_size 0;
- # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
- error_page 418 = @gitlab-workhorse;
- return 418;
- }
-
- # Build artifacts should be submitted to this location
- location ~ ^/[\w\.-]+/[\w\.-]+/builds/download {
- client_max_body_size 0;
- # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
- error_page 418 = @gitlab-workhorse;
- return 418;
- }
-
- # Build artifacts should be submitted to this location
- location ~ /ci/api/v1/builds/[0-9]+/artifacts {
- client_max_body_size 0;
- # 'Error' 418 is a hack to re-use the @gitlab-workhorse block
- error_page 418 = @gitlab-workhorse;
- return 418;
- }
-
- location @gitlab-workhorse {
- client_max_body_size 0;
- ## If you use HTTPS make sure you disable gzip compression
- ## to be safe against BREACH attack.
gzip off;
## https://github.com/gitlabhq/gitlabhq/issues/694
@@ -216,14 +92,7 @@ server {
proxy_connect_timeout 300;
proxy_redirect off;
- # Do not buffer Git HTTP responses
- proxy_buffering off;
-
- # The following settings only work with NGINX 1.7.11 or newer
- #
- # # Pass chunked request bodies to gitlab-workhorse as-is
- # proxy_request_buffering off;
- # proxy_http_version 1.1;
+ proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
@@ -232,18 +101,4 @@ server {
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://gitlab-workhorse;
}
-
- ## Enable gzip compression as per rails guide:
- ## http://guides.rubyonrails.org/asset_pipeline.html#gzip-compression
- ## WARNING: If you are using relative urls remove the block below
- ## See config/application.rb under "Relative url support" for the list of
- ## other files that need to be changed for relative url support
- location ~ ^/(assets)/ {
- root /home/git/gitlab/public;
- gzip_static on; # to serve pre-gzipped version
- expires max;
- add_header Cache-Control public;
- }
-
- error_page 502 /502.html;
}
diff --git a/spec/controllers/projects/tree_controller_spec.rb b/spec/controllers/projects/tree_controller_spec.rb
index a474574c6e5..e74731c9ed8 100644
--- a/spec/controllers/projects/tree_controller_spec.rb
+++ b/spec/controllers/projects/tree_controller_spec.rb
@@ -98,7 +98,7 @@ describe Projects::TreeController do
project_id: project.to_param,
id: 'master',
dir_name: path,
- new_branch: target_branch,
+ target_branch: target_branch,
commit_message: 'Test commit message')
end
@@ -108,8 +108,8 @@ describe Projects::TreeController do
it 'redirects to the new directory' do
expect(subject).
- to redirect_to("/#{project.path_with_namespace}/blob/#{target_branch}/#{path}")
- expect(flash[:notice]).to eq('The directory has been successfully created')
+ to redirect_to("/#{project.path_with_namespace}/tree/#{target_branch}/#{path}")
+ expect(flash[:notice]).to eq('The directory has been successfully created.')
end
end
@@ -119,7 +119,7 @@ describe Projects::TreeController do
it 'does not allow overwriting of existing files' do
expect(subject).
- to redirect_to("/#{project.path_with_namespace}/blob/master")
+ to redirect_to("/#{project.path_with_namespace}/tree/master")
expect(flash[:alert]).to eq('Directory already exists as a file')
end
end
diff --git a/spec/factories.rb b/spec/factories.rb
index 4bf93adabe2..d6b4efa9a03 100644
--- a/spec/factories.rb
+++ b/spec/factories.rb
@@ -43,7 +43,8 @@ FactoryGirl.define do
end
after(:create) do |user, evaluator|
- user.identities << create(:identity,
+ user.identities << create(
+ :identity,
provider: evaluator.provider,
extern_uid: evaluator.extern_uid
)
diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb
index 66a2cc0c157..26d03944b8a 100644
--- a/spec/features/admin/admin_runners_spec.rb
+++ b/spec/features/admin/admin_runners_spec.rb
@@ -63,7 +63,7 @@ describe "Admin Runners" do
end
describe 'runners registration token' do
- let!(:token) { current_application_settings.ensure_runners_registration_token }
+ let!(:token) { current_application_settings.runners_registration_token }
before { visit admin_runners_path }
it 'has a registration token' do
diff --git a/spec/features/ci_lint_spec.rb b/spec/features/ci_lint_spec.rb
new file mode 100644
index 00000000000..e6e73e5e67c
--- /dev/null
+++ b/spec/features/ci_lint_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+describe 'CI Lint' do
+ before do
+ login_as :user
+ end
+
+ describe 'YAML parsing' do
+ before do
+ visit ci_lint_path
+ fill_in 'content', with: yaml_content
+ click_on 'Validate'
+ end
+
+ context 'YAML is correct' do
+ let(:yaml_content) do
+ File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
+ end
+
+ it 'Yaml parsing' do
+ within "table" do
+ expect(page).to have_content('Job - rspec')
+ expect(page).to have_content('Job - spinach')
+ expect(page).to have_content('Deploy Job - staging')
+ expect(page).to have_content('Deploy Job - production')
+ end
+ end
+ end
+
+ context 'YAML is incorrect' do
+ let(:yaml_content) { '' }
+
+ it 'displays information about an error' do
+ expect(page).to have_content('Status: syntax is incorrect')
+ expect(page).to have_content('Error: Please provide content of .gitlab-ci.yml')
+ end
+ end
+ end
+end
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index ecc85376ffc..fe7f07f5b75 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -19,30 +19,13 @@ describe 'Commits' do
let!(:build) { FactoryGirl.create :ci_build, commit: commit }
describe 'Project commits' do
- context 'builds enabled' do
- context '.gitlab-ci.yml found' do
- before do
- visit namespace_project_commits_path(project.namespace, project, :master)
- end
-
- it 'should show build status' do
- page.within("//li[@id='commit-#{commit.short_sha}']") do
- expect(page).to have_css(".ci-status-link")
- end
- end
- end
+ before do
+ visit namespace_project_commits_path(project.namespace, project, :master)
+ end
- context 'no .gitlab-ci.yml found' do
- before do
- stub_ci_commit_yaml_file(nil)
- visit namespace_project_commits_path(project.namespace, project, :master)
- end
-
- it 'should not show build status' do
- page.within("//li[@id='commit-#{commit.short_sha}']") do
- expect(page).to have_no_css(".ci-status-link")
- end
- end
+ it 'should show build status' do
+ page.within("//li[@id='commit-#{commit.short_sha}']") do
+ expect(page).to have_css(".ci-status-link")
end
end
end
diff --git a/spec/features/issues/filter_by_milestone_spec.rb b/spec/features/issues/filter_by_milestone_spec.rb
index f600f8684ac..38c8d343ce3 100644
--- a/spec/features/issues/filter_by_milestone_spec.rb
+++ b/spec/features/issues/filter_by_milestone_spec.rb
@@ -13,7 +13,7 @@ feature 'Issue filtering by Milestone', feature: true do
visit_issues(project)
filter_by_milestone(Milestone::None.title)
- expect(page).to have_css('.issue-title', count: 1)
+ expect(page).to have_css('.title', count: 1)
end
scenario 'filters by a specific Milestone', js: true do
@@ -23,7 +23,7 @@ feature 'Issue filtering by Milestone', feature: true do
visit_issues(project)
filter_by_milestone(milestone.title)
- expect(page).to have_css('.issue-title', count: 1)
+ expect(page).to have_css('.title', count: 1)
end
def visit_issues(project)
diff --git a/spec/features/lint_spec.rb b/spec/features/lint_spec.rb
deleted file mode 100644
index 5d8f56e2cfb..00000000000
--- a/spec/features/lint_spec.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-require 'spec_helper'
-
-describe "Lint" do
- before do
- login_as :user
- end
-
- it "Yaml parsing", js: true do
- content = File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
- visit ci_lint_path
- fill_in "content", with: content
- click_on "Validate"
- within "table" do
- expect(page).to have_content("Job - rspec")
- expect(page).to have_content("Job - spinach")
- expect(page).to have_content("Deploy Job - staging")
- expect(page).to have_content("Deploy Job - production")
- end
- end
-
- it "Yaml parsing with error", js: true do
- visit ci_lint_path
- fill_in "content", with: ""
- click_on "Validate"
- expect(page).to have_content("Status: syntax is incorrect")
- expect(page).to have_content("Error: Please provide content of .gitlab-ci.yml")
- end
-end
diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb
index 922c76285d1..2451e56fe7c 100644
--- a/spec/features/login_spec.rb
+++ b/spec/features/login_spec.rb
@@ -98,4 +98,56 @@ feature 'Login', feature: true do
expect(page).to have_content('Invalid login or password.')
end
end
+
+ describe 'with required two-factor authentication enabled' do
+ let(:user) { create(:user) }
+ before(:each) { stub_application_setting(require_two_factor_authentication: true) }
+
+ context 'with grace period defined' do
+ before(:each) do
+ stub_application_setting(two_factor_grace_period: 48)
+ login_with(user)
+ end
+
+ context 'within the grace period' do
+ it 'redirects to two-factor configuration page' do
+ expect(current_path).to eq new_profile_two_factor_auth_path
+ expect(page).to have_content('You must configure Two-Factor Authentication in your account until')
+ end
+
+ it 'two-factor configuration is skippable' do
+ expect(current_path).to eq new_profile_two_factor_auth_path
+
+ click_link 'Configure it later'
+ expect(current_path).to eq root_path
+ end
+ end
+
+ context 'after the grace period' do
+ let(:user) { create(:user, otp_grace_period_started_at: 9999.hours.ago) }
+
+ it 'redirects to two-factor configuration page' do
+ expect(current_path).to eq new_profile_two_factor_auth_path
+ expect(page).to have_content('You must configure Two-Factor Authentication in your account.')
+ end
+
+ it 'two-factor configuration is not skippable' do
+ expect(current_path).to eq new_profile_two_factor_auth_path
+ expect(page).not_to have_link('Configure it later')
+ end
+ end
+ end
+
+ context 'without grace pariod defined' do
+ before(:each) do
+ stub_application_setting(two_factor_grace_period: 0)
+ login_with(user)
+ end
+
+ it 'redirects to two-factor configuration page' do
+ expect(current_path).to eq new_profile_two_factor_auth_path
+ expect(page).to have_content('You must configure Two-Factor Authentication in your account.')
+ end
+ 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 28a46a0725d..7aa7eb965e9 100644
--- a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb
+++ b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb
@@ -21,12 +21,12 @@ feature 'Merge When Build Succeeds', feature: true, js: true do
end
it 'displays the Merge When Build Succeeds button' do
- expect(page).to have_link "Merge When Build Succeeds"
+ expect(page).to have_button "Merge When Build Succeeds"
end
context "Merge When Build succeeds enabled" do
before do
- click_link "Merge When Build Succeeds"
+ click_button "Merge When Build Succeeds"
end
it 'activates Merge When Build Succeeds feature' do
@@ -58,7 +58,7 @@ feature 'Merge When Build Succeeds', feature: true, js: true do
it 'cancels the automatic merge' do
click_link "Cancel Automatic Merge"
- expect(page).to have_link "Merge When Build Succeeds"
+ expect(page).to have_button "Merge When Build Succeeds"
visit_merge_request(merge_request) # Needed to refresh the page
expect(page).to have_content "Canceled the automatic merge"
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index 09fcff2444a..74b148f5d17 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -70,6 +70,20 @@ 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 { expect(page).to have_content('You have Master access to this project.') }
+ it { expect(page).to have_link('Leave this project') }
+ end
+
def remove_with_confirm(button_text, confirm_with)
click_button button_text
fill_in 'confirm_name_input', with: confirm_with
diff --git a/spec/features/security/group_access_spec.rb b/spec/features/security/group_access_spec.rb
index 4b78e3a61f0..65f8073c693 100644
--- a/spec/features/security/group_access_spec.rb
+++ b/spec/features/security/group_access_spec.rb
@@ -16,11 +16,11 @@ describe 'Group access', feature: true do
end
end
- def group_member(access_level, group = group)
+ def group_member(access_level, grp = group())
level = Object.const_get("Gitlab::Access::#{access_level.upcase}")
create(:user).tap do |user|
- group.add_user(user, level)
+ grp.add_user(user, level)
end
end
diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb
index fca3c77fc64..b7368cca29d 100644
--- a/spec/features/task_lists_spec.rb
+++ b/spec/features/task_lists_spec.rb
@@ -47,7 +47,7 @@ feature 'Task Lists', feature: true do
it 'contains the required selectors' do
visit_issue(project, issue)
- container = '.issue-details .description.js-task-list-container'
+ container = '.detail-page-description .description.js-task-list-container'
expect(page).to have_selector(container)
expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox")
@@ -123,7 +123,7 @@ feature 'Task Lists', feature: true do
it 'contains the required selectors' do
visit_merge_request(project, merge)
- container = '.merge-request-details .description.js-task-list-container'
+ container = '.detail-page-description .description.js-task-list-container'
expect(page).to have_selector(container)
expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox")
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 5568f06639c..68527c3a4f8 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -263,11 +263,12 @@ describe ApplicationHelper do
end
it 'includes a default js-timeago class' do
- expect(element.attr('class')).to eq 'time_ago js-timeago'
+ expect(element.attr('class')).to eq 'time_ago js-timeago js-timeago-pending'
end
it 'accepts a custom html_class' do
- expect(element(html_class: 'custom_class').attr('class')).to eq 'custom_class js-timeago'
+ expect(element(html_class: 'custom_class').attr('class')).
+ to eq 'custom_class js-timeago js-timeago-pending'
end
it 'accepts a custom tooltip placement' do
@@ -278,7 +279,7 @@ describe ApplicationHelper do
el = element.next_element
expect(el.name).to eq 'script'
- expect(el.text).to include "$('.js-timeago').last().timeago()"
+ expect(el.text).to include "$('.js-timeago-pending').removeClass('js-timeago-pending').timeago()"
end
it 'allows the script tag to be excluded' do
diff --git a/spec/helpers/ci_status_helper_spec.rb b/spec/helpers/ci_status_helper_spec.rb
index 7fc53eb1472..4f8d9c67262 100644
--- a/spec/helpers/ci_status_helper_spec.rb
+++ b/spec/helpers/ci_status_helper_spec.rb
@@ -6,13 +6,8 @@ describe CiStatusHelper do
let(:success_commit) { double("Ci::Commit", status: 'success') }
let(:failed_commit) { double("Ci::Commit", status: 'failed') }
- describe 'ci_status_color' do
- it { expect(ci_status_icon(success_commit)).to include('fa-check') }
- it { expect(ci_status_icon(failed_commit)).to include('fa-close') }
- end
-
- describe 'ci_status_color' do
- it { expect(ci_status_color(success_commit)).to eq('green') }
- it { expect(ci_status_color(failed_commit)).to eq('red') }
+ describe 'ci_status_icon' do
+ it { expect(helper.ci_status_icon(success_commit)).to include('fa-check') }
+ it { expect(helper.ci_status_icon(failed_commit)).to include('fa-close') }
end
end
diff --git a/spec/helpers/groups_helper.rb b/spec/helpers/groups_helper.rb
index 5d174460681..4ea90a80a92 100644
--- a/spec/helpers/groups_helper.rb
+++ b/spec/helpers/groups_helper.rb
@@ -9,7 +9,7 @@ describe GroupsHelper do
group.avatar = File.open(avatar_file_path)
group.save!
expect(group_icon(group.path).to_s).
- to match("/uploads/group/avatar/#{ group.id }/banana_sample.gif")
+ to match("/uploads/group/avatar/#{group.id}/banana_sample.gif")
end
it 'should give default avatar_icon when no avatar is present' do
diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb
index 1f2c4ee77b5..ffd8ebae029 100644
--- a/spec/helpers/issues_helper_spec.rb
+++ b/spec/helpers/issues_helper_spec.rb
@@ -127,18 +127,6 @@ describe IssuesHelper do
it { is_expected.to eq("!1, !2, or !3") }
end
- describe "#url_to_emoji" do
- it "returns url" do
- expect(url_to_emoji("smile")).to include("emoji/1F604.png")
- end
- end
-
- describe "#emoji_list" do
- it "returns url" do
- expect(emoji_list).to be_kind_of(Array)
- end
- end
-
describe "#note_active_class" do
before do
@note = create :note
@@ -153,4 +141,11 @@ describe IssuesHelper do
expect(note_active_class(Note.all, @note.author)).to eq("active")
end
end
+
+ describe "#awards_sort" do
+ it "sorts a hash so thumbsup and thumbsdown are always on top" do
+ data = { "thumbsdown" => "some value", "lifter" => "some value", "thumbsup" => "some value" }
+ expect(awards_sort(data).keys).to eq(["thumbsup", "thumbsdown", "lifter"])
+ end
+ end
end
diff --git a/spec/helpers/merge_requests_helper_spec.rb b/spec/helpers/merge_requests_helper_spec.rb
index 0ef1efb8bce..600e1c4e9ec 100644
--- a/spec/helpers/merge_requests_helper_spec.rb
+++ b/spec/helpers/merge_requests_helper_spec.rb
@@ -1,24 +1,57 @@
require 'spec_helper'
describe MergeRequestsHelper do
- describe "#issues_sentence" do
+ describe 'ci_build_details_path' do
+ let(:project) { create :project }
+ let(:merge_request) { MergeRequest.new }
+ let(:ci_service) { CiService.new }
+ let(:last_commit) { Ci::Commit.new({}) }
+
+ before do
+ allow(merge_request).to receive(:source_project).and_return(project)
+ allow(merge_request).to receive(:last_commit).and_return(last_commit)
+ allow(project).to receive(:ci_service).and_return(ci_service)
+ allow(last_commit).to receive(:sha).and_return('12d65c')
+ end
+
+ it 'does not include api credentials in a link' do
+ allow(ci_service).
+ to receive(:build_page).and_return("http://secretuser:secretpass@jenkins.example.com:8888/job/test1/scm/bySHA1/12d65c")
+ expect(helper.ci_build_details_path(merge_request)).to_not match("secret")
+ end
+ end
+
+ describe '#issues_sentence' do
subject { issues_sentence(issues) }
let(:issues) do
[build(:issue, iid: 1), build(:issue, iid: 2), build(:issue, iid: 3)]
end
it { is_expected.to eq('#1, #2, and #3') }
+
+ context 'for JIRA issues' do
+ let(:project) { create(:project) }
+ let(:issues) do
+ [
+ JiraIssue.new('JIRA-123', project),
+ JiraIssue.new('JIRA-456', project),
+ JiraIssue.new('FOOBAR-7890', project)
+ ]
+ end
+
+ it { is_expected.to eq('FOOBAR-7890, JIRA-123, and JIRA-456') }
+ end
end
- describe "#format_mr_branch_names" do
- describe "within the same project" do
+ describe '#format_mr_branch_names' do
+ describe 'within the same project' do
let(:merge_request) { create(:merge_request) }
subject { format_mr_branch_names(merge_request) }
it { is_expected.to eq([merge_request.source_branch, merge_request.target_branch]) }
end
- describe "within different projects" do
+ describe 'within different projects' do
let(:project) { create(:project) }
let(:fork_project) { create(:project, forked_from_project: project) }
let(:merge_request) { create(:merge_request, source_project: fork_project, target_project: project) }
diff --git a/spec/helpers/page_layout_helper_spec.rb b/spec/helpers/page_layout_helper_spec.rb
new file mode 100644
index 00000000000..fd7107779f6
--- /dev/null
+++ b/spec/helpers/page_layout_helper_spec.rb
@@ -0,0 +1,129 @@
+require 'rails_helper'
+
+describe PageLayoutHelper do
+ describe 'page_description' do
+ it 'defaults to value returned by page_description_default helper' do
+ allow(helper).to receive(:page_description_default).and_return('Foo')
+
+ expect(helper.page_description).to eq 'Foo'
+ end
+
+ it 'returns the last-pushed description' do
+ helper.page_description('Foo')
+ helper.page_description('Bar')
+ helper.page_description('Baz')
+
+ expect(helper.page_description).to eq 'Baz'
+ end
+
+ it 'squishes multiple newlines' do
+ helper.page_description("Foo\nBar\nBaz")
+
+ expect(helper.page_description).to eq 'Foo Bar Baz'
+ end
+
+ it 'truncates' do
+ helper.page_description <<-LOREM.strip_heredoc
+ Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo
+ ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis
+ dis parturient montes, nascetur ridiculus mus. Donec quam felis,
+ ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa
+ quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget,
+ arcu.
+ LOREM
+
+ expect(helper.page_description).to end_with 'quam felis,...'
+ end
+
+ it 'sanitizes all HTML' do
+ helper.page_description("<b>Bold</b> <h1>Header</h1>")
+
+ expect(helper.page_description).to eq 'Bold Header'
+ end
+ end
+
+ describe 'page_description_default' do
+ it 'uses Project description when available' do
+ project = double(description: 'Project Description')
+ helper.instance_variable_set(:@project, project)
+
+ expect(helper.page_description_default).to eq 'Project Description'
+ end
+
+ it 'uses brand_title when Project description is nil' do
+ project = double(description: nil)
+ helper.instance_variable_set(:@project, project)
+
+ expect(helper).to receive(:brand_title).and_return('Brand Title')
+ expect(helper.page_description_default).to eq 'Brand Title'
+ end
+
+ it 'falls back to brand_title' do
+ allow(helper).to receive(:brand_title).and_return('Brand Title')
+
+ expect(helper.page_description_default).to eq 'Brand Title'
+ end
+ end
+
+ describe 'page_image' do
+ it 'defaults to the GitLab logo' do
+ expect(helper.page_image).to end_with 'assets/gitlab_logo.png'
+ end
+
+ context 'with @project' do
+ it 'uses Project avatar if available' do
+ project = double(avatar_url: 'http://example.com/uploads/avatar.png')
+ helper.instance_variable_set(:@project, project)
+
+ expect(helper.page_image).to eq project.avatar_url
+ end
+
+ it 'falls back to the default' do
+ project = double(avatar_url: nil)
+ helper.instance_variable_set(:@project, project)
+
+ expect(helper.page_image).to end_with 'assets/gitlab_logo.png'
+ end
+ end
+
+ context 'with @user' do
+ it 'delegates to avatar_icon helper' do
+ user = double('User')
+ helper.instance_variable_set(:@user, user)
+
+ expect(helper).to receive(:avatar_icon).with(user)
+
+ helper.page_image
+ end
+ end
+ end
+
+ describe 'page_card_attributes' do
+ it 'raises ArgumentError when given more than two attributes' do
+ map = { foo: 'foo', bar: 'bar', baz: 'baz' }
+
+ expect { helper.page_card_attributes(map) }.
+ to raise_error(ArgumentError, /more than two attributes/)
+ end
+
+ it 'rejects blank values' do
+ map = { foo: 'foo', bar: '' }
+ helper.page_card_attributes(map)
+
+ expect(helper.page_card_attributes).to eq({ foo: 'foo' })
+ end
+ end
+
+ describe 'page_card_meta_tags' do
+ it 'returns the twitter:label and twitter:data tags' do
+ allow(helper).to receive(:page_card_attributes).and_return(foo: 'bar')
+
+ tags = helper.page_card_meta_tags
+
+ aggregate_failures do
+ expect(tags).to include %q(<meta property="twitter:label1" content="foo" />)
+ expect(tags).to include %q(<meta property="twitter:data1" content="bar" />)
+ end
+ end
+ end
+end
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index f2efb528aeb..53207767581 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -53,6 +53,16 @@ describe ProjectsHelper do
end
end
+ describe 'user_max_access_in_project' do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ before do
+ project.team.add_user(user, Gitlab::Access::MASTER)
+ end
+
+ it { expect(helper.user_max_access_in_project(user.id, project)).to eq('Master') }
+ end
+
describe "readme_cache_key" do
let(:project) { create(:project) }
diff --git a/spec/javascripts/fixtures/issues_show.html.haml b/spec/javascripts/fixtures/issues_show.html.haml
index 7e8b2a64351..470cabeafbb 100644
--- a/spec/javascripts/fixtures/issues_show.html.haml
+++ b/spec/javascripts/fixtures/issues_show.html.haml
@@ -1,6 +1,16 @@
-%a.btn-close
+:css
+ .hidden { display: none !important; }
-.issue-details
+.flash-container
+ .flash-alert
+ .flash-notice
+
+.status-box.status-box-open Open
+.status-box.status-box-closed.hidden Closed
+%a.btn-close{"href" => "http://gitlab.com/issues/6/close"} Close
+%a.btn-reopen.hidden{"href" => "http://gitlab.com/issues/6/reopen"} Reopen
+
+.detail-page-description
.description.js-task-list-container
.wiki
%ul.task-list
diff --git a/spec/javascripts/fixtures/merge_requests_show.html.haml b/spec/javascripts/fixtures/merge_requests_show.html.haml
index f0c622935f8..8447dfdda32 100644
--- a/spec/javascripts/fixtures/merge_requests_show.html.haml
+++ b/spec/javascripts/fixtures/merge_requests_show.html.haml
@@ -1,6 +1,6 @@
%a.btn-close
-.merge-request-details
+.detail-page-description
.description.js-task-list-container
.wiki
%ul.task-list
diff --git a/spec/javascripts/fixtures/new_branch.html.haml b/spec/javascripts/fixtures/new_branch.html.haml
new file mode 100644
index 00000000000..f06629e5ecc
--- /dev/null
+++ b/spec/javascripts/fixtures/new_branch.html.haml
@@ -0,0 +1,4 @@
+%form.js-create-branch-form
+ %input.js-branch-name
+ .js-branch-name-error
+ %input{id: "ref"}
diff --git a/spec/javascripts/issue_spec.js.coffee b/spec/javascripts/issue_spec.js.coffee
index 268e4c68c31..7e67c778861 100644
--- a/spec/javascripts/issue_spec.js.coffee
+++ b/spec/javascripts/issue_spec.js.coffee
@@ -20,3 +20,89 @@ describe 'Issue', ->
expect(req.data.issue.description).not.toBe(null)
$('.js-task-list-field').trigger('tasklist:changed')
+describe 'reopen/close issue', ->
+ fixture.preload('issues_show.html')
+ beforeEach ->
+ fixture.load('issues_show.html')
+ @issue = new Issue()
+ it 'closes an issue', ->
+ $.ajax = (obj) ->
+ expect(obj.type).toBe('PUT')
+ expect(obj.url).toBe('http://gitlab.com/issues/6/close')
+ obj.success saved: true
+
+ $btnClose = $('a.btn-close')
+ $btnReopen = $('a.btn-reopen')
+ expect($btnReopen).toBeHidden()
+ expect($btnClose.text()).toBe('Close')
+ expect(typeof $btnClose.prop('disabled')).toBe('undefined')
+
+ $btnClose.trigger('click')
+
+ expect($btnReopen).toBeVisible()
+ expect($btnClose).toBeHidden()
+ expect($('div.status-box-closed')).toBeVisible()
+ expect($('div.status-box-open')).toBeHidden()
+
+ it 'fails to closes an issue with success:false', ->
+
+ $.ajax = (obj) ->
+ expect(obj.type).toBe('PUT')
+ expect(obj.url).toBe('http://goesnowhere.nothing/whereami')
+ obj.success saved: false
+
+ $btnClose = $('a.btn-close')
+ $btnReopen = $('a.btn-reopen')
+ $btnClose.attr('href','http://goesnowhere.nothing/whereami')
+ expect($btnReopen).toBeHidden()
+ expect($btnClose.text()).toBe('Close')
+ expect(typeof $btnClose.prop('disabled')).toBe('undefined')
+
+ $btnClose.trigger('click')
+
+ expect($btnReopen).toBeHidden()
+ expect($btnClose).toBeVisible()
+ expect($('div.status-box-closed')).toBeHidden()
+ expect($('div.status-box-open')).toBeVisible()
+ expect($('div.flash-alert')).toBeVisible()
+ expect($('div.flash-alert').text()).toBe('Unable to update this issue at this time.')
+
+ it 'fails to closes an issue with HTTP error', ->
+
+ $.ajax = (obj) ->
+ expect(obj.type).toBe('PUT')
+ expect(obj.url).toBe('http://goesnowhere.nothing/whereami')
+ obj.error()
+
+ $btnClose = $('a.btn-close')
+ $btnReopen = $('a.btn-reopen')
+ $btnClose.attr('href','http://goesnowhere.nothing/whereami')
+ expect($btnReopen).toBeHidden()
+ expect($btnClose.text()).toBe('Close')
+ expect(typeof $btnClose.prop('disabled')).toBe('undefined')
+
+ $btnClose.trigger('click')
+
+ expect($btnReopen).toBeHidden()
+ expect($btnClose).toBeVisible()
+ expect($('div.status-box-closed')).toBeHidden()
+ expect($('div.status-box-open')).toBeVisible()
+ expect($('div.flash-alert')).toBeVisible()
+ expect($('div.flash-alert').text()).toBe('Unable to update this issue at this time.')
+
+ it 'reopens an issue', ->
+ $.ajax = (obj) ->
+ expect(obj.type).toBe('PUT')
+ expect(obj.url).toBe('http://gitlab.com/issues/6/reopen')
+ obj.success saved: true
+
+ $btnClose = $('a.btn-close')
+ $btnReopen = $('a.btn-reopen')
+ expect($btnReopen.text()).toBe('Reopen')
+
+ $btnReopen.trigger('click')
+
+ expect($btnReopen).toBeHidden()
+ expect($btnClose).toBeVisible()
+ expect($('div.status-box-open')).toBeVisible()
+ expect($('div.status-box-closed')).toBeHidden() \ No newline at end of file
diff --git a/spec/javascripts/new_branch_spec.js.coffee b/spec/javascripts/new_branch_spec.js.coffee
new file mode 100644
index 00000000000..f2ce85efcdc
--- /dev/null
+++ b/spec/javascripts/new_branch_spec.js.coffee
@@ -0,0 +1,160 @@
+#= require jquery-ui
+#= require new_branch_form
+
+describe 'Branch', ->
+ describe 'create a new branch', ->
+ fixture.preload('new_branch.html')
+
+ fillNameWith = (value) ->
+ $('.js-branch-name').val(value).trigger('blur')
+
+ expectToHaveError = (error) ->
+ expect($('.js-branch-name-error span').text()).toEqual(error)
+
+ beforeEach ->
+ fixture.load('new_branch.html')
+ $('form').on 'submit', (e) -> e.preventDefault()
+
+ @form = new NewBranchForm($('.js-create-branch-form'), [])
+
+ it "can't start with a dot", ->
+ fillNameWith '.foo'
+ expectToHaveError "can't start with '.'"
+
+ it "can't start with a slash", ->
+ fillNameWith '/foo'
+ expectToHaveError "can't start with '/'"
+
+ it "can't have two consecutive dots", ->
+ fillNameWith 'foo..bar'
+ expectToHaveError "can't contain '..'"
+
+ it "can't have spaces anywhere", ->
+ fillNameWith ' foo'
+ expectToHaveError "can't contain spaces"
+ fillNameWith 'foo bar'
+ expectToHaveError "can't contain spaces"
+ fillNameWith 'foo '
+ expectToHaveError "can't contain spaces"
+
+ it "can't have ~ anywhere", ->
+ fillNameWith '~foo'
+ expectToHaveError "can't contain '~'"
+ fillNameWith 'foo~bar'
+ expectToHaveError "can't contain '~'"
+ fillNameWith 'foo~'
+ expectToHaveError "can't contain '~'"
+
+ it "can't have tilde anwhere", ->
+ fillNameWith '~foo'
+ expectToHaveError "can't contain '~'"
+ fillNameWith 'foo~bar'
+ expectToHaveError "can't contain '~'"
+ fillNameWith 'foo~'
+ expectToHaveError "can't contain '~'"
+
+ it "can't have caret anywhere", ->
+ fillNameWith '^foo'
+ expectToHaveError "can't contain '^'"
+ fillNameWith 'foo^bar'
+ expectToHaveError "can't contain '^'"
+ fillNameWith 'foo^'
+ expectToHaveError "can't contain '^'"
+
+ it "can't have : anywhere", ->
+ fillNameWith ':foo'
+ expectToHaveError "can't contain ':'"
+ fillNameWith 'foo:bar'
+ expectToHaveError "can't contain ':'"
+ fillNameWith ':foo'
+ expectToHaveError "can't contain ':'"
+
+ it "can't have question mark anywhere", ->
+ fillNameWith '?foo'
+ expectToHaveError "can't contain '?'"
+ fillNameWith 'foo?bar'
+ expectToHaveError "can't contain '?'"
+ fillNameWith 'foo?'
+ expectToHaveError "can't contain '?'"
+
+ it "can't have asterisk anywhere", ->
+ fillNameWith '*foo'
+ expectToHaveError "can't contain '*'"
+ fillNameWith 'foo*bar'
+ expectToHaveError "can't contain '*'"
+ fillNameWith 'foo*'
+ expectToHaveError "can't contain '*'"
+
+ it "can't have open bracket anywhere", ->
+ fillNameWith '[foo'
+ expectToHaveError "can't contain '['"
+ fillNameWith 'foo[bar'
+ expectToHaveError "can't contain '['"
+ fillNameWith 'foo['
+ expectToHaveError "can't contain '['"
+
+ it "can't have a backslash anywhere", ->
+ fillNameWith '\\foo'
+ expectToHaveError "can't contain '\\'"
+ fillNameWith 'foo\\bar'
+ expectToHaveError "can't contain '\\'"
+ fillNameWith 'foo\\'
+ expectToHaveError "can't contain '\\'"
+
+ it "can't contain a sequence @{ anywhere", ->
+ fillNameWith '@{foo'
+ expectToHaveError "can't contain '@{'"
+ fillNameWith 'foo@{bar'
+ expectToHaveError "can't contain '@{'"
+ fillNameWith 'foo@{'
+ expectToHaveError "can't contain '@{'"
+
+ it "can't have consecutive slashes", ->
+ fillNameWith 'foo//bar'
+ expectToHaveError "can't contain consecutive slashes"
+
+ it "can't end with a slash", ->
+ fillNameWith 'foo/'
+ expectToHaveError "can't end in '/'"
+
+ it "can't end with a dot", ->
+ fillNameWith 'foo.'
+ expectToHaveError "can't end in '.'"
+
+ it "can't end with .lock", ->
+ fillNameWith 'foo.lock'
+ expectToHaveError "can't end in '.lock'"
+
+ it "can't be the single character @", ->
+ fillNameWith '@'
+ expectToHaveError "can't be '@'"
+
+ it "concatenates all error messages", ->
+ fillNameWith '/foo bar?~.'
+ expectToHaveError "can't start with '/', can't contain spaces, '?', '~', can't end in '.'"
+
+ it "doesn't duplicate error messages", ->
+ fillNameWith '?foo?bar?zoo?'
+ expectToHaveError "can't contain '?'"
+
+ it "removes the error message when is a valid name", ->
+ fillNameWith 'foo?bar'
+ expect($('.js-branch-name-error span').length).toEqual(1)
+ fillNameWith 'foobar'
+ expect($('.js-branch-name-error span').length).toEqual(0)
+
+ it "can have dashes anywhere", ->
+ fillNameWith '-foo-bar-zoo-'
+ expect($('.js-branch-name-error span').length).toEqual(0)
+
+ it "can have underscores anywhere", ->
+ fillNameWith '_foo_bar_zoo_'
+ expect($('.js-branch-name-error span').length).toEqual(0)
+
+ it "can have numbers anywhere", ->
+ fillNameWith '1foo2bar3zoo4'
+ expect($('.js-branch-name-error span').length).toEqual(0)
+
+ it "can be only letters", ->
+ fillNameWith 'foo'
+ expect($('.js-branch-name-error span').length).toEqual(0)
diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb
index 3534bf97784..8bdebae1841 100644
--- a/spec/lib/banzai/filter/user_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb
@@ -37,9 +37,22 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do
.to eq urls.namespace_project_url(project.namespace, project)
end
- it 'adds to the results hash' do
- result = reference_pipeline_result("Hey #{reference}")
- expect(result[:references][:user]).to eq [project.creator]
+ context "when the author is a member of the project" do
+
+ it 'adds to the results hash' do
+ result = reference_pipeline_result("Hey #{reference}", author: project.creator)
+ expect(result[:references][:user]).to eq [project.creator]
+ end
+ end
+
+ context "when the author is not a member of the project" do
+
+ let(:other_user) { create(:user) }
+
+ it "doesn't add to the results hash" do
+ result = reference_pipeline_result("Hey #{reference}", author: other_user)
+ expect(result[:references][:user]).to eq []
+ end
end
end
diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
index c90133fbf03..d15100fc6d8 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
module Ci
describe GitlabCiYamlProcessor, lib: true do
let(:path) { 'path' }
-
+
describe "#builds_for_ref" do
let(:type) { 'test' }
@@ -29,7 +29,7 @@ module Ci
when: "on_success"
})
end
-
+
describe :only do
it "does not return builds if only has another branch" do
config = YAML.dump({
@@ -517,7 +517,7 @@ module Ci
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Unknown parameter: extra")
end
- it "returns errors if there is no any jobs defined" do
+ it "returns errors if there are no jobs defined" do
config = YAML.dump({ before_script: ["bundle update"] })
expect do
GitlabCiYamlProcessor.new(config, path)
diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb
index 3bba5e2efa2..1e755259dae 100644
--- a/spec/lib/gitlab/ldap/user_spec.rb
+++ b/spec/lib/gitlab/ldap/user_spec.rb
@@ -42,6 +42,21 @@ describe Gitlab::LDAP::User, lib: true do
end
end
+ describe '.find_by_uid_and_provider' do
+ it 'retrieves the correct user' do
+ special_info = {
+ name: 'John Åström',
+ email: 'john@example.com',
+ nickname: 'jastrom'
+ }
+ special_hash = OmniAuth::AuthHash.new(uid: 'CN=John Åström,CN=Users,DC=Example,DC=com', provider: 'ldapmain', info: special_info)
+ special_chars_user = described_class.new(special_hash)
+ user = special_chars_user.save
+
+ expect(described_class.find_by_uid_and_provider(special_hash.uid, special_hash.provider)).to eq user
+ end
+ end
+
describe :find_or_create do
it "finds the user if already existing" do
create(:omniauth_user, extern_uid: 'my-uid', provider: 'ldapmain')
diff --git a/spec/lib/gitlab/metrics/delta_spec.rb b/spec/lib/gitlab/metrics/delta_spec.rb
new file mode 100644
index 00000000000..718387cdee1
--- /dev/null
+++ b/spec/lib/gitlab/metrics/delta_spec.rb
@@ -0,0 +1,16 @@
+require 'spec_helper'
+
+describe Gitlab::Metrics::Delta do
+ let(:delta) { described_class.new }
+
+ describe '#compared_with' do
+ it 'returns the delta as a Numeric' do
+ expect(delta.compared_with(5)).to eq(5)
+ end
+
+ it 'bases the delta on a previously used value' do
+ expect(delta.compared_with(5)).to eq(5)
+ expect(delta.compared_with(15)).to eq(10)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/instrumentation_spec.rb b/spec/lib/gitlab/metrics/instrumentation_spec.rb
new file mode 100644
index 00000000000..a7eab9d11cc
--- /dev/null
+++ b/spec/lib/gitlab/metrics/instrumentation_spec.rb
@@ -0,0 +1,234 @@
+require 'spec_helper'
+
+describe Gitlab::Metrics::Instrumentation do
+ let(:transaction) { Gitlab::Metrics::Transaction.new }
+
+ before do
+ @dummy = Class.new do
+ def self.foo(text = 'foo')
+ text
+ end
+
+ def bar(text = 'bar')
+ text
+ end
+ end
+
+ allow(@dummy).to receive(:name).and_return('Dummy')
+ end
+
+ describe '.configure' do
+ it 'yields self' do
+ described_class.configure do |c|
+ expect(c).to eq(described_class)
+ end
+ end
+ end
+
+ describe '.instrument_method' do
+ describe 'with metrics enabled' do
+ before do
+ allow(Gitlab::Metrics).to receive(:enabled?).and_return(true)
+
+ described_class.instrument_method(@dummy, :foo)
+ end
+
+ it 'renames the original method' do
+ expect(@dummy).to respond_to(:_original_foo)
+ end
+
+ it 'calls the instrumented method with the correct arguments' do
+ expect(@dummy.foo).to eq('foo')
+ end
+
+ it 'tracks the call duration upon calling the method' do
+ allow(Gitlab::Metrics).to receive(:method_call_threshold).
+ and_return(0)
+
+ allow(described_class).to receive(:transaction).
+ and_return(transaction)
+
+ expect(transaction).to receive(:add_metric).
+ with(described_class::SERIES, an_instance_of(Hash),
+ method: 'Dummy.foo')
+
+ @dummy.foo
+ end
+
+ it 'does not track method calls below a given duration threshold' do
+ allow(Gitlab::Metrics).to receive(:method_call_threshold).
+ and_return(100)
+
+ expect(transaction).to_not receive(:add_metric)
+
+ @dummy.foo
+ end
+ end
+
+ describe 'with metrics disabled' do
+ before do
+ allow(Gitlab::Metrics).to receive(:enabled?).and_return(false)
+ end
+
+ it 'does not instrument the method' do
+ described_class.instrument_method(@dummy, :foo)
+
+ expect(@dummy).to_not respond_to(:_original_foo)
+ end
+ end
+ end
+
+ describe '.instrument_instance_method' do
+ describe 'with metrics enabled' do
+ before do
+ allow(Gitlab::Metrics).to receive(:enabled?).and_return(true)
+
+ described_class.
+ instrument_instance_method(@dummy, :bar)
+ end
+
+ it 'renames the original method' do
+ expect(@dummy.method_defined?(:_original_bar)).to eq(true)
+ end
+
+ it 'calls the instrumented method with the correct arguments' do
+ expect(@dummy.new.bar).to eq('bar')
+ end
+
+ it 'tracks the call duration upon calling the method' do
+ allow(Gitlab::Metrics).to receive(:method_call_threshold).
+ and_return(0)
+
+ allow(described_class).to receive(:transaction).
+ and_return(transaction)
+
+ expect(transaction).to receive(:add_metric).
+ with(described_class::SERIES, an_instance_of(Hash),
+ method: 'Dummy#bar')
+
+ @dummy.new.bar
+ end
+
+ it 'does not track method calls below a given duration threshold' do
+ allow(Gitlab::Metrics).to receive(:method_call_threshold).
+ and_return(100)
+
+ expect(transaction).to_not receive(:add_metric)
+
+ @dummy.new.bar
+ end
+ end
+
+ describe 'with metrics disabled' do
+ before do
+ allow(Gitlab::Metrics).to receive(:enabled?).and_return(false)
+ end
+
+ it 'does not instrument the method' do
+ described_class.
+ instrument_instance_method(@dummy, :bar)
+
+ expect(@dummy.method_defined?(:_original_bar)).to eq(false)
+ end
+ end
+ end
+
+ describe '.instrument_class_hierarchy' do
+ before do
+ allow(Gitlab::Metrics).to receive(:enabled?).and_return(true)
+
+ @child1 = Class.new(@dummy) do
+ def self.child1_foo; end
+ def child1_bar; end
+ end
+
+ @child2 = Class.new(@child1) do
+ def self.child2_foo; end
+ def child2_bar; end
+ end
+ end
+
+ it 'recursively instruments a class hierarchy' do
+ described_class.instrument_class_hierarchy(@dummy)
+
+ expect(@child1).to respond_to(:_original_child1_foo)
+ expect(@child2).to respond_to(:_original_child2_foo)
+
+ expect(@child1.method_defined?(:_original_child1_bar)).to eq(true)
+ expect(@child2.method_defined?(:_original_child2_bar)).to eq(true)
+ end
+
+ it 'does not instrument the root module' do
+ described_class.instrument_class_hierarchy(@dummy)
+
+ expect(@dummy).to_not respond_to(:_original_foo)
+ expect(@dummy.method_defined?(:_original_bar)).to eq(false)
+ end
+ end
+
+ describe '.instrument_methods' do
+ before do
+ allow(Gitlab::Metrics).to receive(:enabled?).and_return(true)
+ end
+
+ it 'instruments all public class methods' do
+ described_class.instrument_methods(@dummy)
+
+ expect(@dummy).to respond_to(:_original_foo)
+ end
+
+ it 'only instruments methods directly defined in the module' do
+ mod = Module.new do
+ def kittens
+ end
+ end
+
+ @dummy.extend(mod)
+
+ described_class.instrument_methods(@dummy)
+
+ expect(@dummy).to_not respond_to(:_original_kittens)
+ end
+
+ it 'can take a block to determine if a method should be instrumented' do
+ described_class.instrument_methods(@dummy) do
+ false
+ end
+
+ expect(@dummy).to_not respond_to(:_original_foo)
+ end
+ end
+
+ describe '.instrument_instance_methods' do
+ before do
+ allow(Gitlab::Metrics).to receive(:enabled?).and_return(true)
+ end
+
+ it 'instruments all public instance methods' do
+ described_class.instrument_instance_methods(@dummy)
+
+ expect(@dummy.method_defined?(:_original_bar)).to eq(true)
+ end
+
+ it 'only instruments methods directly defined in the module' do
+ mod = Module.new do
+ def kittens
+ end
+ end
+
+ @dummy.include(mod)
+
+ described_class.instrument_instance_methods(@dummy)
+
+ expect(@dummy.method_defined?(:_original_kittens)).to eq(false)
+ end
+
+ it 'can take a block to determine if a method should be instrumented' do
+ described_class.instrument_instance_methods(@dummy) do
+ false
+ end
+
+ expect(@dummy.method_defined?(:_original_bar)).to eq(false)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/metric_spec.rb b/spec/lib/gitlab/metrics/metric_spec.rb
new file mode 100644
index 00000000000..aa76315c79c
--- /dev/null
+++ b/spec/lib/gitlab/metrics/metric_spec.rb
@@ -0,0 +1,54 @@
+require 'spec_helper'
+
+describe Gitlab::Metrics::Metric do
+ let(:metric) do
+ described_class.new('foo', { number: 10 }, { host: 'localtoast' })
+ end
+
+ describe '#series' do
+ subject { metric.series }
+
+ it { is_expected.to eq('foo') }
+ end
+
+ describe '#values' do
+ subject { metric.values }
+
+ it { is_expected.to eq({ number: 10 }) }
+ end
+
+ describe '#tags' do
+ subject { metric.tags }
+
+ it { is_expected.to eq({ host: 'localtoast' }) }
+ end
+
+ describe '#to_hash' do
+ it 'returns a Hash' do
+ expect(metric.to_hash).to be_an_instance_of(Hash)
+ end
+
+ describe 'the returned Hash' do
+ let(:hash) { metric.to_hash }
+
+ it 'includes the series' do
+ expect(hash[:series]).to eq('foo')
+ end
+
+ it 'includes the tags' do
+ expect(hash[:tags]).to be_an_instance_of(Hash)
+
+ expect(hash[:tags][:hostname]).to be_an_instance_of(String)
+ expect(hash[:tags][:process_type]).to be_an_instance_of(String)
+ end
+
+ it 'includes the values' do
+ expect(hash[:values]).to eq({ number: 10 })
+ end
+
+ it 'includes the timestamp' do
+ expect(hash[:timestamp]).to be_an_instance_of(Fixnum)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/obfuscated_sql_spec.rb b/spec/lib/gitlab/metrics/obfuscated_sql_spec.rb
new file mode 100644
index 00000000000..2b681c9fe34
--- /dev/null
+++ b/spec/lib/gitlab/metrics/obfuscated_sql_spec.rb
@@ -0,0 +1,93 @@
+require 'spec_helper'
+
+describe Gitlab::Metrics::ObfuscatedSQL do
+ describe '#to_s' do
+ it 'replaces newlines with a space' do
+ sql = described_class.new("SELECT x\nFROM y")
+
+ expect(sql.to_s).to eq('SELECT x FROM y')
+ end
+
+ describe 'using single values' do
+ it 'replaces a single integer' do
+ sql = described_class.new('SELECT x FROM y WHERE a = 10')
+
+ expect(sql.to_s).to eq('SELECT x FROM y WHERE a = ?')
+ end
+
+ it 'replaces a single float' do
+ sql = described_class.new('SELECT x FROM y WHERE a = 10.5')
+
+ expect(sql.to_s).to eq('SELECT x FROM y WHERE a = ?')
+ end
+
+ it 'replaces a single quoted string' do
+ sql = described_class.new("SELECT x FROM y WHERE a = 'foo'")
+
+ expect(sql.to_s).to eq('SELECT x FROM y WHERE a = ?')
+ end
+
+ if Gitlab::Database.mysql?
+ it 'replaces a double quoted string' do
+ sql = described_class.new('SELECT x FROM y WHERE a = "foo"')
+
+ expect(sql.to_s).to eq('SELECT x FROM y WHERE a = ?')
+ end
+ end
+
+ it 'replaces a single regular expression' do
+ sql = described_class.new('SELECT x FROM y WHERE a = /foo/')
+
+ expect(sql.to_s).to eq('SELECT x FROM y WHERE a = ?')
+ end
+
+ it 'replaces regular expressions using escaped slashes' do
+ sql = described_class.new('SELECT x FROM y WHERE a = /foo\/bar/')
+
+ expect(sql.to_s).to eq('SELECT x FROM y WHERE a = ?')
+ end
+ end
+
+ describe 'using consecutive values' do
+ it 'replaces multiple integers' do
+ sql = described_class.new('SELECT x FROM y WHERE z IN (10, 20, 30)')
+
+ expect(sql.to_s).to eq('SELECT x FROM y WHERE z IN (3 values)')
+ end
+
+ it 'replaces multiple floats' do
+ sql = described_class.new('SELECT x FROM y WHERE z IN (1.5, 2.5, 3.5)')
+
+ expect(sql.to_s).to eq('SELECT x FROM y WHERE z IN (3 values)')
+ end
+
+ it 'replaces multiple single quoted strings' do
+ sql = described_class.new("SELECT x FROM y WHERE z IN ('foo', 'bar')")
+
+ expect(sql.to_s).to eq('SELECT x FROM y WHERE z IN (2 values)')
+ end
+
+ if Gitlab::Database.mysql?
+ it 'replaces multiple double quoted strings' do
+ sql = described_class.new('SELECT x FROM y WHERE z IN ("foo", "bar")')
+
+ expect(sql.to_s).to eq('SELECT x FROM y WHERE z IN (2 values)')
+ end
+ end
+
+ it 'replaces multiple regular expressions' do
+ sql = described_class.new('SELECT x FROM y WHERE z IN (/foo/, /bar/)')
+
+ expect(sql.to_s).to eq('SELECT x FROM y WHERE z IN (2 values)')
+ end
+ end
+
+ if Gitlab::Database.postgresql?
+ it 'replaces double quotes' do
+ sql = described_class.new('SELECT "x" FROM "y"')
+
+ expect(sql.to_s).to eq('SELECT x FROM y')
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/rack_middleware_spec.rb b/spec/lib/gitlab/metrics/rack_middleware_spec.rb
new file mode 100644
index 00000000000..a143fe4cfcd
--- /dev/null
+++ b/spec/lib/gitlab/metrics/rack_middleware_spec.rb
@@ -0,0 +1,63 @@
+require 'spec_helper'
+
+describe Gitlab::Metrics::RackMiddleware do
+ let(:app) { double(:app) }
+
+ let(:middleware) { described_class.new(app) }
+
+ let(:env) { { 'REQUEST_METHOD' => 'GET', 'REQUEST_URI' => '/foo' } }
+
+ describe '#call' do
+ before do
+ expect_any_instance_of(Gitlab::Metrics::Transaction).to receive(:finish)
+ end
+
+ it 'tracks a transaction' do
+ expect(app).to receive(:call).with(env).and_return('yay')
+
+ expect(middleware.call(env)).to eq('yay')
+ end
+
+ it 'tags a transaction with the name and action of a controller' do
+ klass = double(:klass, name: 'TestController')
+ controller = double(:controller, class: klass, action_name: 'show')
+
+ env['action_controller.instance'] = controller
+
+ allow(app).to receive(:call).with(env)
+
+ expect(middleware).to receive(:tag_controller).
+ with(an_instance_of(Gitlab::Metrics::Transaction), env)
+
+ middleware.call(env)
+ end
+ end
+
+ describe '#transaction_from_env' do
+ let(:transaction) { middleware.transaction_from_env(env) }
+
+ it 'returns a Transaction' do
+ expect(transaction).to be_an_instance_of(Gitlab::Metrics::Transaction)
+ end
+
+ it 'tags the transaction with the request method and URI' do
+ expect(transaction.tags[:request_method]).to eq('GET')
+ expect(transaction.tags[:request_uri]).to eq('/foo')
+ end
+ end
+
+ describe '#tag_controller' do
+ let(:transaction) { middleware.transaction_from_env(env) }
+
+ it 'tags a transaction with the name and action of a controller' do
+ klass = double(:klass, name: 'TestController')
+ controller = double(:controller, class: klass, action_name: 'show')
+
+ env['action_controller.instance'] = controller
+
+ middleware.tag_controller(transaction, env)
+
+ expect(transaction.tags[:action]).to eq('TestController#show')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/sampler_spec.rb b/spec/lib/gitlab/metrics/sampler_spec.rb
new file mode 100644
index 00000000000..51a941c48cd
--- /dev/null
+++ b/spec/lib/gitlab/metrics/sampler_spec.rb
@@ -0,0 +1,97 @@
+require 'spec_helper'
+
+describe Gitlab::Metrics::Sampler do
+ let(:sampler) { described_class.new(5) }
+
+ after do
+ Allocations.stop if Gitlab::Metrics.mri?
+ end
+
+ describe '#start' do
+ it 'gathers a sample at a given interval' do
+ expect(sampler).to receive(:sleep).with(5)
+ expect(sampler).to receive(:sample)
+ expect(sampler).to receive(:loop).and_yield
+
+ sampler.start.join
+ end
+ end
+
+ describe '#sample' do
+ it 'samples various statistics' do
+ expect(sampler).to receive(:sample_memory_usage)
+ expect(sampler).to receive(:sample_file_descriptors)
+ expect(sampler).to receive(:sample_objects)
+ expect(sampler).to receive(:sample_gc)
+ expect(sampler).to receive(:flush)
+
+ sampler.sample
+ end
+
+ it 'clears any GC profiles' do
+ expect(sampler).to receive(:flush)
+ expect(GC::Profiler).to receive(:clear)
+
+ sampler.sample
+ end
+ end
+
+ describe '#flush' do
+ it 'schedules the metrics using Sidekiq' do
+ expect(Gitlab::Metrics).to receive(:submit_metrics).
+ with([an_instance_of(Hash)])
+
+ sampler.sample_memory_usage
+ sampler.flush
+ end
+ end
+
+ describe '#sample_memory_usage' do
+ it 'adds a metric containing the memory usage' do
+ expect(Gitlab::Metrics::System).to receive(:memory_usage).
+ and_return(9000)
+
+ expect(Gitlab::Metrics::Metric).to receive(:new).
+ with('memory_usage', value: 9000).
+ and_call_original
+
+ sampler.sample_memory_usage
+ end
+ end
+
+ describe '#sample_file_descriptors' do
+ it 'adds a metric containing the amount of open file descriptors' do
+ expect(Gitlab::Metrics::System).to receive(:file_descriptor_count).
+ and_return(4)
+
+ expect(Gitlab::Metrics::Metric).to receive(:new).
+ with('file_descriptors', value: 4).
+ and_call_original
+
+ sampler.sample_file_descriptors
+ end
+ end
+
+ describe '#sample_objects' do
+ it 'adds a metric containing the amount of allocated objects' do
+ expect(Gitlab::Metrics::Metric).to receive(:new).
+ with('object_counts', an_instance_of(Hash), an_instance_of(Hash)).
+ at_least(:once).
+ and_call_original
+
+ sampler.sample_objects
+ end
+ end
+
+ describe '#sample_gc' do
+ it 'adds a metric containing garbage collection statistics' do
+ expect(GC::Profiler).to receive(:total_time).and_return(0.24)
+
+ expect(Gitlab::Metrics::Metric).to receive(:new).
+ with('gc_statistics', an_instance_of(Hash)).
+ and_call_original
+
+ sampler.sample_gc
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb b/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb
new file mode 100644
index 00000000000..5882e7d81c7
--- /dev/null
+++ b/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe Gitlab::Metrics::SidekiqMiddleware do
+ let(:middleware) { described_class.new }
+
+ describe '#call' do
+ it 'tracks the transaction' do
+ worker = Class.new.new
+
+ expect_any_instance_of(Gitlab::Metrics::Transaction).to receive(:finish)
+
+ middleware.call(worker, 'test', :test) { nil }
+ end
+ end
+
+ describe '#tag_worker' do
+ it 'adds the worker class and action to the transaction' do
+ trans = Gitlab::Metrics::Transaction.new
+ worker = double(:worker, class: double(:class, name: 'TestWorker'))
+
+ expect(trans).to receive(:add_tag).with(:action, 'TestWorker#perform')
+
+ middleware.tag_worker(trans, worker)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb
new file mode 100644
index 00000000000..c6cd584663f
--- /dev/null
+++ b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+describe Gitlab::Metrics::Subscribers::ActionView do
+ let(:transaction) { Gitlab::Metrics::Transaction.new }
+
+ let(:subscriber) { described_class.new }
+
+ let(:event) do
+ root = Rails.root.to_s
+
+ double(:event, duration: 2.1,
+ payload: { identifier: "#{root}/app/views/x.html.haml" })
+ end
+
+ before do
+ allow(subscriber).to receive(:current_transaction).and_return(transaction)
+
+ allow(Gitlab::Metrics).to receive(:last_relative_application_frame).
+ and_return(['app/views/x.html.haml', 4])
+ end
+
+ describe '#render_template' do
+ it 'tracks rendering of a template' do
+ values = { duration: 2.1 }
+ tags = {
+ view: 'app/views/x.html.haml',
+ file: 'app/views/x.html.haml',
+ line: 4
+ }
+
+ expect(transaction).to receive(:add_metric).
+ with(described_class::SERIES, values, tags)
+
+ subscriber.render_template(event)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
new file mode 100644
index 00000000000..05b6cc14716
--- /dev/null
+++ b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe Gitlab::Metrics::Subscribers::ActiveRecord do
+ let(:transaction) { Gitlab::Metrics::Transaction.new }
+
+ let(:subscriber) { described_class.new }
+
+ let(:event) do
+ double(:event, duration: 0.2,
+ payload: { sql: 'SELECT * FROM users WHERE id = 10' })
+ end
+
+ before do
+ allow(subscriber).to receive(:current_transaction).and_return(transaction)
+
+ allow(Gitlab::Metrics).to receive(:last_relative_application_frame).
+ and_return(['app/models/foo.rb', 4])
+ end
+
+ describe '#sql' do
+ it 'tracks the execution of a SQL query' do
+ sql = 'SELECT * FROM users WHERE id = ?'
+ values = { duration: 0.2 }
+ tags = { sql: sql, file: 'app/models/foo.rb', line: 4 }
+
+ expect(transaction).to receive(:add_metric).
+ with(described_class::SERIES, values, tags)
+
+ subscriber.sql(event)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/system_spec.rb b/spec/lib/gitlab/metrics/system_spec.rb
new file mode 100644
index 00000000000..f8c1d956ca1
--- /dev/null
+++ b/spec/lib/gitlab/metrics/system_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+describe Gitlab::Metrics::System do
+ if File.exist?('/proc')
+ describe '.memory_usage' do
+ it "returns the process' memory usage in bytes" do
+ expect(described_class.memory_usage).to be > 0
+ end
+ end
+
+ describe '.file_descriptor_count' do
+ it 'returns the amount of open file descriptors' do
+ expect(described_class.file_descriptor_count).to be > 0
+ end
+ end
+ else
+ describe '.memory_usage' do
+ it 'returns 0.0' do
+ expect(described_class.memory_usage).to eq(0.0)
+ end
+ end
+
+ describe '.file_descriptor_count' do
+ it 'returns 0' do
+ expect(described_class.file_descriptor_count).to eq(0)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/transaction_spec.rb b/spec/lib/gitlab/metrics/transaction_spec.rb
new file mode 100644
index 00000000000..6862fc9e2d1
--- /dev/null
+++ b/spec/lib/gitlab/metrics/transaction_spec.rb
@@ -0,0 +1,77 @@
+require 'spec_helper'
+
+describe Gitlab::Metrics::Transaction do
+ let(:transaction) { described_class.new }
+
+ describe '#duration' do
+ it 'returns the duration of a transaction in seconds' do
+ transaction.run { sleep(0.5) }
+
+ expect(transaction.duration).to be >= 0.5
+ end
+ end
+
+ describe '#run' do
+ it 'yields the supplied block' do
+ expect { |b| transaction.run(&b) }.to yield_control
+ end
+
+ it 'stores the transaction in the current thread' do
+ transaction.run do
+ expect(Thread.current[described_class::THREAD_KEY]).to eq(transaction)
+ end
+ end
+
+ it 'removes the transaction from the current thread upon completion' do
+ transaction.run { }
+
+ expect(Thread.current[described_class::THREAD_KEY]).to be_nil
+ end
+ end
+
+ describe '#add_metric' do
+ it 'adds a metric tagged with the transaction UUID' do
+ expect(Gitlab::Metrics::Metric).to receive(:new).
+ with('foo', { number: 10 }, { transaction_id: transaction.uuid })
+
+ transaction.add_metric('foo', number: 10)
+ end
+ end
+
+ describe '#add_tag' do
+ it 'adds a tag' do
+ transaction.add_tag(:foo, 'bar')
+
+ expect(transaction.tags).to eq({ foo: 'bar' })
+ end
+ end
+
+ describe '#finish' do
+ it 'tracks the transaction details and submits them to Sidekiq' do
+ expect(transaction).to receive(:track_self)
+ expect(transaction).to receive(:submit)
+
+ transaction.finish
+ end
+ end
+
+ describe '#track_self' do
+ it 'adds a metric for the transaction itself' do
+ expect(transaction).to receive(:add_metric).
+ with(described_class::SERIES, { duration: transaction.duration }, {})
+
+ transaction.track_self
+ end
+ end
+
+ describe '#submit' do
+ it 'submits the metrics to Sidekiq' do
+ transaction.track_self
+
+ expect(Gitlab::Metrics).to receive(:submit_metrics).
+ with([an_instance_of(Hash)])
+
+ transaction.submit
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics_spec.rb b/spec/lib/gitlab/metrics_spec.rb
new file mode 100644
index 00000000000..6c0682cac4d
--- /dev/null
+++ b/spec/lib/gitlab/metrics_spec.rb
@@ -0,0 +1,84 @@
+require 'spec_helper'
+
+describe Gitlab::Metrics do
+ describe '.pool_size' do
+ it 'returns a Fixnum' do
+ expect(described_class.pool_size).to be_an_instance_of(Fixnum)
+ end
+ end
+
+ describe '.timeout' do
+ it 'returns a Fixnum' do
+ expect(described_class.timeout).to be_an_instance_of(Fixnum)
+ end
+ end
+
+ describe '.enabled?' do
+ it 'returns a boolean' do
+ expect([true, false].include?(described_class.enabled?)).to eq(true)
+ end
+ end
+
+ describe '.hostname' do
+ it 'returns a String containing the hostname' do
+ expect(described_class.hostname).to eq(Socket.gethostname)
+ end
+ end
+
+ describe '.last_relative_application_frame' do
+ it 'returns an Array containing a file path and line number' do
+ file, line = described_class.last_relative_application_frame
+
+ expect(line).to eq(__LINE__ - 2)
+ expect(file).to eq('spec/lib/gitlab/metrics_spec.rb')
+ end
+ end
+
+ describe '#submit_metrics' do
+ it 'prepares and writes the metrics to InfluxDB' do
+ connection = double(:connection)
+ pool = double(:pool)
+
+ expect(pool).to receive(:with).and_yield(connection)
+ expect(connection).to receive(:write_points).with(an_instance_of(Array))
+ expect(Gitlab::Metrics).to receive(:pool).and_return(pool)
+
+ described_class.submit_metrics([{ 'series' => 'kittens', 'tags' => {} }])
+ end
+ end
+
+ describe '#prepare_metrics' do
+ it 'returns a Hash with the keys as Symbols' do
+ metrics = described_class.
+ prepare_metrics([{ 'values' => {}, 'tags' => {} }])
+
+ expect(metrics).to eq([{ values: {}, tags: {} }])
+ end
+
+ it 'escapes tag values' do
+ metrics = described_class.prepare_metrics([
+ { 'values' => {}, 'tags' => { 'foo' => 'bar=' } }
+ ])
+
+ expect(metrics).to eq([{ values: {}, tags: { 'foo' => 'bar\\=' } }])
+ end
+
+ it 'drops empty tags' do
+ metrics = described_class.prepare_metrics([
+ { 'values' => {}, 'tags' => { 'cats' => '', 'dogs' => nil } }
+ ])
+
+ expect(metrics).to eq([{ values: {}, tags: {} }])
+ end
+ end
+
+ describe '#escape_value' do
+ it 'escapes an equals sign' do
+ expect(described_class.escape_value('foo=')).to eq('foo\\=')
+ end
+
+ it 'casts values to Strings' do
+ expect(described_class.escape_value(10)).to eq('10')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb
index 66dc5d4911d..7d963795e17 100644
--- a/spec/lib/gitlab/reference_extractor_spec.rb
+++ b/spec/lib/gitlab/reference_extractor_spec.rb
@@ -97,6 +97,16 @@ describe Gitlab::ReferenceExtractor, lib: true do
expect(extracted.first.commit_to).to eq commit
end
+ context 'with an external issue tracker' do
+ let(:project) { create(:jira_project) }
+ subject { described_class.new(project, project.creator) }
+
+ it 'returns JIRA issues for a JIRA-integrated project' do
+ subject.analyze('JIRA-123 and FOOBAR-4567')
+ expect(subject.issues).to eq [JiraIssue.new('JIRA-123', project), JiraIssue.new('FOOBAR-4567', project)]
+ end
+ end
+
context 'with a project with an underscore' do
let(:other_project) { create(:project, path: 'test_project') }
let(:issue) { create(:issue, project: other_project) }
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 5f64453a35f..35d8220ae54 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -27,6 +27,7 @@
# admin_notification_email :string(255)
# shared_runners_enabled :boolean default(TRUE), not null
# max_artifacts_size :integer default(100), not null
+# runners_registration_token :string(255)
#
require 'spec_helper'
diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb
index 96b6f1dbca6..1c22e3cb7c4 100644
--- a/spec/models/build_spec.rb
+++ b/spec/models/build_spec.rb
@@ -189,6 +189,12 @@ describe Ci::Build, models: true do
it { is_expected.to eq(98.29) }
end
+
+ context 'using a regex capture' do
+ subject { build.extract_coverage('TOTAL 9926 3489 65%', 'TOTAL\s+\d+\s+\d+\s+(\d{1,3}\%)') }
+
+ it { is_expected.to eq(65) }
+ end
end
describe :variables do
@@ -390,4 +396,68 @@ describe Ci::Build, models: true do
it { is_expected.to include('gitlab-ci-token') }
it { is_expected.to include(project.web_url[7..-1]) }
end
+
+ def create_mr(build, commit, factory: :merge_request, created_at: Time.now)
+ FactoryGirl.create(factory,
+ source_project_id: commit.gl_project_id,
+ target_project_id: commit.gl_project_id,
+ source_branch: build.ref,
+ created_at: created_at)
+ end
+
+ describe :merge_request do
+ context 'when a MR has a reference to the commit' do
+ before do
+ @merge_request = create_mr(build, commit, factory: :merge_request)
+
+ commits = [double(id: commit.sha)]
+ allow(@merge_request).to receive(:commits).and_return(commits)
+ allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request])
+ end
+
+ it 'returns the single associated MR' do
+ expect(build.merge_request.id).to eq(@merge_request.id)
+ end
+ end
+
+ context 'when there is not a MR referencing the commit' do
+ it 'returns nil' do
+ expect(build.merge_request).to be_nil
+ end
+ end
+
+ context 'when more than one MR have a reference to the commit' do
+ before do
+ @merge_request = create_mr(build, commit, factory: :merge_request)
+ @merge_request.close!
+ @merge_request2 = create_mr(build, commit, factory: :merge_request)
+
+ commits = [double(id: commit.sha)]
+ allow(@merge_request).to receive(:commits).and_return(commits)
+ allow(@merge_request2).to receive(:commits).and_return(commits)
+ allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request, @merge_request2])
+ end
+
+ it 'returns the first MR' do
+ expect(build.merge_request.id).to eq(@merge_request.id)
+ end
+ end
+
+ context 'when a Build is created after the MR' do
+ before do
+ @merge_request = create_mr(build, commit, factory: :merge_request_with_diffs)
+ commit2 = FactoryGirl.create :ci_commit, project: project
+ @build2 = FactoryGirl.create :ci_build, commit: commit2
+
+ commits = [double(id: commit.sha), double(id: commit2.sha)]
+ allow(@merge_request).to receive(:commits).and_return(commits)
+ allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request])
+ end
+
+ it 'returns the current MR' do
+ expect(@build2.merge_request.id).to eq(@merge_request.id)
+ end
+ end
+
+ end
end
diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb
index ac61c8fb525..b193e16e7f8 100644
--- a/spec/models/ci/commit_spec.rb
+++ b/spec/models/ci/commit_spec.rb
@@ -37,14 +37,14 @@ describe Ci::Commit, models: true do
it 'returns ordered list of commits' do
commit1 = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, project: project
- commit2 = FactoryGirl.create :ci_commit, committed_at: 2.hour.ago, project: project
+ commit2 = FactoryGirl.create :ci_commit, committed_at: 2.hours.ago, project: project
expect(project.ci_commits.ordered).to eq([commit2, commit1])
end
it 'returns commits ordered by committed_at and id, with nulls last' do
commit1 = FactoryGirl.create :ci_commit, committed_at: 1.hour.ago, project: project
commit2 = FactoryGirl.create :ci_commit, committed_at: nil, project: project
- commit3 = FactoryGirl.create :ci_commit, committed_at: 2.hour.ago, project: project
+ commit3 = FactoryGirl.create :ci_commit, committed_at: 2.hours.ago, project: project
commit4 = FactoryGirl.create :ci_commit, committed_at: nil, project: project
expect(project.ci_commits.ordered).to eq([commit2, commit4, commit3, commit1])
end
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 0f13c4410cd..021d62cdf0c 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -81,4 +81,36 @@ describe Issue, "Issuable" do
expect(hook_data[:object_attributes]).to eq(issue.hook_attrs)
end
end
+
+ describe '#card_attributes' do
+ it 'includes the author name' do
+ allow(issue).to receive(:author).and_return(double(name: 'Robert'))
+ allow(issue).to receive(:assignee).and_return(nil)
+
+ expect(issue.card_attributes).
+ to eq({ 'Author' => 'Robert', 'Assignee' => nil })
+ end
+
+ it 'includes the assignee name' do
+ allow(issue).to receive(:author).and_return(double(name: 'Robert'))
+ allow(issue).to receive(:assignee).and_return(double(name: 'Douwe'))
+
+ expect(issue.card_attributes).
+ to eq({ 'Author' => 'Robert', 'Assignee' => 'Douwe' })
+ end
+ end
+
+ describe "votes" do
+ before do
+ author = create :user
+ project = create :empty_project
+ issue.notes.awards.create!(note: "thumbsup", author: author, project: project)
+ issue.notes.awards.create!(note: "thumbsdown", author: author, project: project)
+ end
+
+ it "returns correct values" do
+ expect(issue.upvotes).to eq(1)
+ expect(issue.downvotes).to eq(1)
+ end
+ end
end
diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb
index 6179882e935..20f0c561e44 100644
--- a/spec/models/concerns/mentionable_spec.rb
+++ b/spec/models/concerns/mentionable_spec.rb
@@ -1,5 +1,22 @@
require 'spec_helper'
+describe Mentionable do
+ include Mentionable
+
+ def author
+ nil
+ end
+
+ describe :references do
+ let(:project) { create(:project) }
+
+ it 'excludes JIRA references' do
+ allow(project).to receive_messages(jira_tracker?: true)
+ expect(referenced_mentionables(project, 'JIRA-123')).to be_empty
+ end
+ end
+end
+
describe Issue, "Mentionable" do
describe '#mentioned_users' do
let!(:user) { create(:user, username: 'stranger') }
diff --git a/spec/models/concerns/token_authenticatable_spec.rb b/spec/models/concerns/token_authenticatable_spec.rb
index a9b0b64e5de..30c0a04b840 100644
--- a/spec/models/concerns/token_authenticatable_spec.rb
+++ b/spec/models/concerns/token_authenticatable_spec.rb
@@ -2,7 +2,8 @@ require 'spec_helper'
shared_examples 'TokenAuthenticatable' do
describe 'dynamically defined methods' do
- it { expect(described_class).to be_private_method_defined(:generate_token_for) }
+ it { expect(described_class).to be_private_method_defined(:generate_token) }
+ it { expect(described_class).to be_private_method_defined(:write_new_token) }
it { expect(described_class).to respond_to("find_by_#{token_field}") }
it { is_expected.to respond_to("ensure_#{token_field}") }
it { is_expected.to respond_to("reset_#{token_field}!") }
@@ -24,11 +25,11 @@ describe ApplicationSetting, 'TokenAuthenticatable' do
it_behaves_like 'TokenAuthenticatable'
describe 'generating new token' do
- subject { described_class.new }
- let(:token) { subject.send(token_field) }
-
context 'token is not generated yet' do
- it { expect(token).to be nil }
+ describe 'token field accessor' do
+ subject { described_class.new.send(token_field) }
+ it { is_expected.to_not be_blank }
+ end
describe 'ensured token' do
subject { described_class.new.send("ensure_#{token_field}") }
@@ -36,11 +37,21 @@ describe ApplicationSetting, 'TokenAuthenticatable' do
it { is_expected.to be_a String }
it { is_expected.to_not be_blank }
end
+
+ describe 'ensured! token' do
+ subject { described_class.new.send("ensure_#{token_field}!") }
+
+ it 'should persist new token' do
+ expect(subject).to eq described_class.current[token_field]
+ end
+ end
end
context 'token is generated' do
before { subject.send("reset_#{token_field}!") }
- it { expect(token).to be_a String }
+ it 'persists a new token 'do
+ expect(subject.send(:read_attribute, token_field)).to be_a String
+ end
end
end
diff --git a/spec/models/global_milestone_spec.rb b/spec/models/global_milestone_spec.rb
index ba03e6aabd0..197c99cd007 100644
--- a/spec/models/global_milestone_spec.rb
+++ b/spec/models/global_milestone_spec.rb
@@ -62,4 +62,14 @@ describe GlobalMilestone, models: true do
expect(@global_milestone.milestones.count).to eq(3)
end
end
+
+ describe :safe_title do
+ let(:milestone) { create(:milestone, title: "git / test", project: project1) }
+
+ it 'should strip out slashes and spaces' do
+ global_milestone = GlobalMilestone.new(milestone.title, [milestone])
+
+ expect(global_milestone.safe_title).to eq('git-test')
+ end
+ end
end
diff --git a/spec/models/jira_issue_spec.rb b/spec/models/jira_issue_spec.rb
new file mode 100644
index 00000000000..1634265b439
--- /dev/null
+++ b/spec/models/jira_issue_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+describe JiraIssue do
+ let(:project) { create(:project) }
+ subject { JiraIssue.new('JIRA-123', project) }
+
+ describe 'id' do
+ subject { super().id }
+ it { is_expected.to eq('JIRA-123') }
+ end
+
+ describe 'iid' do
+ subject { super().iid }
+ it { is_expected.to eq('JIRA-123') }
+ end
+
+ describe 'to_s' do
+ subject { super().to_s }
+ it { is_expected.to eq('JIRA-123') }
+ end
+
+ describe :== do
+ specify { expect(subject).to eq(JiraIssue.new('JIRA-123', project)) }
+ specify { expect(subject).not_to eq(JiraIssue.new('JIRA-124', project)) }
+
+ it 'only compares with JiraIssues' do
+ expect(subject).not_to eq('JIRA-123')
+ end
+ end
+end
diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb
index d7fe01976d8..c962b83644a 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -81,7 +81,7 @@ describe Key, models: true do
it 'rejects the multiple line key' do
key = build(:key)
- key.key.gsub!(' ', "\n")
+ key.key.tr!(' ', "\n")
expect(key).not_to be_valid
end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 1aeba9b2b3b..e0653a8327d 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -164,6 +164,17 @@ describe MergeRequest, models: true do
expect(subject.closes_issues).to include(issue2)
end
+
+ context 'for a project with JIRA integration' do
+ let(:issue0) { JiraIssue.new('JIRA-123', subject.project) }
+ let(:issue1) { JiraIssue.new('FOOBAR-4567', subject.project) }
+
+ it 'returns sorted JiraIssues' do
+ allow(subject.project).to receive_messages(default_branch: subject.target_branch)
+
+ expect(subject.closes_issues).to eq([issue0, issue1])
+ end
+ end
end
describe "#work_in_progress?" do
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 216c7dabae0..593d8f76215 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -137,9 +137,14 @@ describe Note, models: true do
create :note, note: "smile", is_award: true
end
- it "returns grouped array of notes" do
- expect(Note.grouped_awards.first.first).to eq("smile")
- expect(Note.grouped_awards.first.last).to match_array(Note.all)
+ it "returns grouped hash of notes" do
+ expect(Note.grouped_awards.keys.size).to eq(3)
+ expect(Note.grouped_awards["smile"]).to match_array(Note.all)
+ end
+
+ it "returns thumbsup and thumbsdown always" do
+ expect(Note.grouped_awards["thumbsup"]).to match_array(Note.none)
+ expect(Note.grouped_awards["thumbsdown"]).to match_array(Note.none)
end
end
@@ -164,8 +169,8 @@ describe Note, models: true do
let(:issue) { create :issue }
it "converts aliases to actual name" do
- note = create :note, note: ":thumbsup:", noteable: issue
- expect(note.reload.note).to eq("+1")
+ note = create :note, note: ":+1:", noteable: issue
+ expect(note.reload.note).to eq("thumbsup")
end
end
end
diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb
index a5662b08bda..91dd92b7c67 100644
--- a/spec/models/project_services/hipchat_service_spec.rb
+++ b/spec/models/project_services/hipchat_service_spec.rb
@@ -57,23 +57,21 @@ describe HipchatService, models: true do
it 'should use v1 if version is provided' do
allow(hipchat).to receive(:api_version).and_return('v1')
- expect(HipChat::Client).to receive(:new).
- with(token,
- api_version: 'v1',
- server_url: server_url).
- and_return(
- double(:hipchat_service).as_null_object)
+ expect(HipChat::Client).to receive(:new).with(
+ token,
+ api_version: 'v1',
+ server_url: server_url
+ ).and_return(double(:hipchat_service).as_null_object)
hipchat.execute(push_sample_data)
end
it 'should use v2 as the version when nothing is provided' do
allow(hipchat).to receive(:api_version).and_return('')
- expect(HipChat::Client).to receive(:new).
- with(token,
- api_version: 'v2',
- server_url: server_url).
- and_return(
- double(:hipchat_service).as_null_object)
+ expect(HipChat::Client).to receive(:new).with(
+ token,
+ api_version: 'v2',
+ server_url: server_url
+ ).and_return(double(:hipchat_service).as_null_object)
hipchat.execute(push_sample_data)
end
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index 7d91ebe9ce6..2f8193170ae 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -26,6 +26,113 @@ describe JiraService, models: true do
it { is_expected.to have_one :service_hook }
end
+ describe "Execute" do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:merge_request) { create(:merge_request) }
+
+ before do
+ @jira_service = JiraService.new
+ allow(@jira_service).to receive_messages(
+ project_id: project.id,
+ project: project,
+ service_hook: true,
+ project_url: 'http://jira.example.com',
+ username: 'gitlab_jira_username',
+ password: 'gitlab_jira_password'
+ )
+ @jira_service.save # will build API URL, as api_url was not specified above
+ @sample_data = Gitlab::PushDataBuilder.build_sample(project, user)
+ # https://github.com/bblimke/webmock#request-with-basic-authentication
+ @api_url = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/issue/JIRA-123/transitions'
+ @comment_url = 'http://gitlab_jira_username:gitlab_jira_password@jira.example.com/rest/api/2/issue/JIRA-123/comment'
+
+ WebMock.stub_request(:post, @api_url)
+ WebMock.stub_request(:post, @comment_url)
+ end
+
+ it "should call JIRA API" do
+ @jira_service.execute(merge_request, JiraIssue.new("JIRA-123", project))
+ expect(WebMock).to have_requested(:post, @comment_url).with(
+ body: /Issue solved with/
+ ).once
+ end
+
+ it "calls the api with jira_issue_transition_id" do
+ @jira_service.jira_issue_transition_id = 'this-is-a-custom-id'
+ @jira_service.execute(merge_request, JiraIssue.new("JIRA-123", project))
+ expect(WebMock).to have_requested(:post, @api_url).with(
+ body: /this-is-a-custom-id/
+ ).once
+ end
+ end
+
+ describe "Stored password invalidation" do
+ let(:project) { create(:project) }
+
+ context "when a password was previously set" do
+ before do
+ @jira_service = JiraService.create(
+ project: create(:project),
+ properties: {
+ api_url: 'http://jira.example.com/rest/api/2',
+ username: 'mic',
+ password: "password"
+ }
+ )
+ end
+
+ it "reset password if url changed" do
+ @jira_service.api_url = 'http://jira_edited.example.com/rest/api/2'
+ @jira_service.save
+ expect(@jira_service.password).to be_nil
+ end
+
+ it "does not reset password if username changed" do
+ @jira_service.username = "some_name"
+ @jira_service.save
+ expect(@jira_service.password).to eq("password")
+ end
+
+ it "does not reset password if new url is set together with password, even if it's the same password" do
+ @jira_service.api_url = 'http://jira_edited.example.com/rest/api/2'
+ @jira_service.password = 'password'
+ @jira_service.save
+ expect(@jira_service.password).to eq("password")
+ expect(@jira_service.api_url).to eq("http://jira_edited.example.com/rest/api/2")
+ end
+
+ it "should reset password if url changed, even if setter called multiple times" do
+ @jira_service.api_url = 'http://jira1.example.com/rest/api/2'
+ @jira_service.api_url = 'http://jira1.example.com/rest/api/2'
+ @jira_service.save
+ expect(@jira_service.password).to be_nil
+ end
+ end
+
+ context "when no password was previously set" do
+ before do
+ @jira_service = JiraService.create(
+ project: create(:project),
+ properties: {
+ api_url: 'http://jira.example.com/rest/api/2',
+ username: 'mic'
+ }
+ )
+ end
+
+ it "saves password if new url is set together with password" do
+ @jira_service.api_url = 'http://jira_edited.example.com/rest/api/2'
+ @jira_service.password = 'password'
+ @jira_service.save
+ 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
@@ -78,11 +185,12 @@ describe JiraService, models: true do
context 'when gitlab.yml was initialized' do
before do
- settings = { "jira" => {
- "title" => "Jira",
- "project_url" => "http://jira.sample/projects/project_a",
- "issues_url" => "http://jira.sample/issues/:id",
- "new_issue_url" => "http://jira.sample/projects/project_a/issues/new"
+ settings = {
+ "jira" => {
+ "title" => "Jira",
+ "project_url" => "http://jira.sample/projects/project_a",
+ "issues_url" => "http://jira.sample/issues/:id",
+ "new_issue_url" => "http://jira.sample/projects/project_a/issues/new"
}
}
allow(Gitlab.config).to receive(:issues_tracker).and_return(settings)
diff --git a/spec/models/project_services/slack_service/note_message_spec.rb b/spec/models/project_services/slack_service/note_message_spec.rb
index ebf8837570e..06006b9a4f5 100644
--- a/spec/models/project_services/slack_service/note_message_spec.rb
+++ b/spec/models/project_services/slack_service/note_message_spec.rb
@@ -89,10 +89,10 @@ describe SlackService::NoteMessage, models: true do
it 'returns a message regarding notes on an issue' do
message = SlackService::NoteMessage.new(@args)
expect(message.pretext).to eq(
- "Test User commented on " \
- "<url|issue #20> in <somewhere.com|project_name>: " \
- "*issue title*")
- expected_attachments = [
+ "Test User commented on " \
+ "<url|issue #20> in <somewhere.com|project_name>: " \
+ "*issue title*")
+ expected_attachments = [
{
text: "comment on an issue",
color: color,
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 87582e07494..400bdf2d962 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -172,13 +172,17 @@ describe Project, models: true do
describe '#get_issue' do
let(:project) { create(:empty_project) }
- let(:issue) { create(:issue, project: project) }
+ let!(:issue) { create(:issue, project: project) }
context 'with default issues tracker' do
it 'returns an issue' do
expect(project.get_issue(issue.iid)).to eq issue
end
+ it 'returns count of open issues' do
+ expect(project.open_issues_count).to eq(1)
+ end
+
it 'returns nil when no issue found' do
expect(project.get_issue(999)).to be_nil
end
@@ -548,4 +552,28 @@ describe Project, models: true do
end
end
end
+
+ describe '#visibility_level_allowed?' do
+ let(:project) { create :project, visibility_level: Gitlab::VisibilityLevel::INTERNAL }
+
+ context 'when checking on non-forked project' do
+ it { expect(project.visibility_level_allowed?(Gitlab::VisibilityLevel::PRIVATE)).to be_truthy }
+ it { expect(project.visibility_level_allowed?(Gitlab::VisibilityLevel::INTERNAL)).to be_truthy }
+ it { expect(project.visibility_level_allowed?(Gitlab::VisibilityLevel::PUBLIC)).to be_truthy }
+ end
+
+ context 'when checking on forked project' do
+ let(:forked_project) { create :forked_project_with_submodules }
+
+ before do
+ forked_project.build_forked_project_link(forked_to_project_id: forked_project.id, forked_from_project_id: project.id)
+ forked_project.save
+ end
+
+ it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::PRIVATE)).to be_truthy }
+ it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::INTERNAL)).to be_truthy }
+ it { expect(forked_project.visibility_level_allowed?(Gitlab::VisibilityLevel::PUBLIC)).to be_falsey }
+ end
+
+ end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index daa9d1087bf..2f184bbaf92 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -26,6 +26,7 @@
# bio :string(255)
# failed_attempts :integer default(0)
# locked_at :datetime
+# unlock_token :string(255)
# username :string(255)
# can_create_group :boolean default(TRUE), not null
# can_create_team :boolean default(TRUE), not null
@@ -462,8 +463,8 @@ describe User, models: true do
expect(User.search(user1.username.downcase).to_a).to eq([user1])
expect(User.search(user2.username.upcase).to_a).to eq([user2])
expect(User.search(user2.username.downcase).to_a).to eq([user2])
- expect(User.search(user1.username.downcase).to_a.count).to eq(2)
- expect(User.search(user2.username.downcase).to_a.count).to eq(1)
+ expect(User.search(user1.username.downcase).to_a.size).to eq(2)
+ expect(User.search(user2.username.downcase).to_a.size).to eq(1)
end
end
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb
index 5c1b58535cc..36461e84c3a 100644
--- a/spec/requests/api/branches_spec.rb
+++ b/spec/requests/api/branches_spec.rb
@@ -118,7 +118,7 @@ describe API::API, api: true do
branch_name: 'new design',
ref: branch_sha
expect(response.status).to eq(400)
- expect(json_response['message']).to eq('Branch name invalid')
+ expect(json_response['message']).to eq('Branch name is invalid')
end
it 'should return 400 if branch already exists' do
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index a91fa735321..e194eb93cf4 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -6,7 +6,7 @@ describe API::API, api: true do
let(:user) { create(:user) }
let!(:project) {create(:project, creator_id: user.id, namespace: user.namespace) }
let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time) }
- let!(:merge_request_closed) { create(:merge_request, state: "closed", author: user, assignee: user, source_project: project, target_project: project, title: "Closed test", created_at: base_time + 1.seconds) }
+ let!(:merge_request_closed) { create(:merge_request, state: "closed", author: user, assignee: user, source_project: project, target_project: project, title: "Closed test", created_at: base_time + 1.second) }
let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, target_project: project, title: "Merged test", created_at: base_time + 2.seconds) }
let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") }
let!(:note2) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "another comment on a MR") }
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index e784b7d1f2d..7f0f9454b10 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -65,6 +65,22 @@ describe API::API, api: true do
expect(json_response.first.keys).to include('tag_list')
end
+ it 'should include open_issues_count' do
+ get api('/projects', user)
+ expect(response.status).to eq 200
+ expect(json_response).to be_an Array
+ expect(json_response.first.keys).to include('open_issues_count')
+ end
+
+ it 'should not include open_issues_count' do
+ project.update_attributes( { issues_enabled: false } )
+
+ get api('/projects', user)
+ expect(response.status).to eq 200
+ expect(json_response).to be_an Array
+ expect(json_response.first.keys).not_to include('open_issues_count')
+ end
+
context 'and using search' do
it 'should return searched project' do
get api('/projects', user), { search: project.name }
@@ -115,6 +131,7 @@ describe API::API, api: true do
expect(json_response).to satisfy do |response|
response.one? do |entry|
+ entry.has_key?('permissions') &&
entry['name'] == project.name &&
entry['owner']['username'] == user.username
end
@@ -123,6 +140,25 @@ describe API::API, api: true do
end
end
+ describe 'GET /projects/starred' do
+ before do
+ admin.starred_projects << project
+ admin.save!
+ end
+
+ it 'should return the starred projects' do
+ get api('/projects/all', admin)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+
+ expect(json_response).to satisfy do |response|
+ response.one? do |entry|
+ entry['name'] == project.name
+ end
+ end
+ end
+ end
+
describe 'POST /projects' do
context 'maximum number of projects reached' do
it 'should not create new project and respond with 403' do
@@ -347,6 +383,18 @@ describe API::API, api: true do
end
describe 'permissions' do
+ context 'all projects' do
+ it 'Contains permission information' do
+ project.team << [user, :master]
+ get api("/projects", user)
+
+ expect(response.status).to eq(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
+ end
+ end
+
context 'personal project' do
it 'Sets project access and returns 200' do
project.team << [user, :master]
@@ -455,7 +503,7 @@ describe API::API, api: true do
end
end
- describe 'PUT /projects/:id/snippets/:shippet_id' do
+ describe 'PUT /projects/:id/snippets/:snippet_id' do
it 'should update an existing project snippet' do
put api("/projects/#{project.id}/snippets/#{snippet.id}", user),
code: 'updated code'
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
index b180d2fec77..fed9ae1949b 100644
--- a/spec/requests/api/services_spec.rb
+++ b/spec/requests/api/services_spec.rb
@@ -29,7 +29,7 @@ describe API::API, api: true do
if required_attributes.empty?
expected_code = 200
else
- attrs.delete(required_attributes.shuffle.first)
+ attrs.delete(required_attributes.sample)
expected_code = 400
end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 2f609c63330..4f278551d07 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -27,6 +27,13 @@ describe API::API, api: true do
user['username'] == username
end['username']).to eq(username)
end
+
+ it "should return one user" do
+ get api("/users?username=#{omniauth_user.username}", user)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response.first['username']).to eq(omniauth_user.username)
+ end
end
context "when admin" do
diff --git a/spec/requests/ci/api/runners_spec.rb b/spec/requests/ci/api/runners_spec.rb
index 567da013e6f..5942aa7a1b5 100644
--- a/spec/requests/ci/api/runners_spec.rb
+++ b/spec/requests/ci/api/runners_spec.rb
@@ -8,7 +8,6 @@ describe Ci::API::API do
before do
stub_gitlab_calls
- stub_application_setting(ensure_runners_registration_token: registration_token)
stub_application_setting(runners_registration_token: registration_token)
end
diff --git a/spec/services/create_commit_builds_service_spec.rb b/spec/services/create_commit_builds_service_spec.rb
index 798c480b81a..ea5dcfa068a 100644
--- a/spec/services/create_commit_builds_service_spec.rb
+++ b/spec/services/create_commit_builds_service_spec.rb
@@ -17,7 +17,7 @@ describe CreateCommitBuildsService, services: true do
before: '00000000',
after: '31das312',
commits: [{ message: "Message" }]
- )
+ )
end
it { expect(commit).to be_kind_of(Ci::Commit) }
@@ -34,7 +34,7 @@ describe CreateCommitBuildsService, services: true do
before: '00000000',
after: '31das312',
commits: [{ message: "Message" }]
- )
+ )
expect(result).to be_persisted
end
@@ -47,26 +47,24 @@ describe CreateCommitBuildsService, services: true do
before: '00000000',
after: '31das312',
commits: [{ message: "Message" }]
- )
+ )
expect(result).to be_persisted
end
end
- it 'skips commits without .gitlab-ci.yml' do
+ it 'skips creating ci_commit for refs without .gitlab-ci.yml' do
stub_ci_commit_yaml_file(nil)
result = service.execute(project, user,
ref: 'refs/heads/0_1',
before: '00000000',
after: '31das312',
commits: [{ message: 'Message' }]
- )
- expect(result).to be_persisted
- expect(result.builds.any?).to be_falsey
- expect(result.status).to eq('skipped')
- expect(result.yaml_errors).to be_nil
+ )
+ expect(result).to be_falsey
+ expect(Ci::Commit.count).to eq(0)
end
- it 'skips commits if yaml is invalid' do
+ it 'fails commits if yaml is invalid' do
message = 'message'
allow_any_instance_of(Ci::Commit).to receive(:git_commit_message) { message }
stub_ci_commit_yaml_file('invalid: file: file')
@@ -76,7 +74,8 @@ describe CreateCommitBuildsService, services: true do
before: '00000000',
after: '31das312',
commits: commits
- )
+ )
+ expect(commit).to be_persisted
expect(commit.builds.any?).to be false
expect(commit.status).to eq('failed')
expect(commit.yaml_errors).to_not be_nil
@@ -96,7 +95,8 @@ describe CreateCommitBuildsService, services: true do
before: '00000000',
after: '31das312',
commits: commits
- )
+ )
+ expect(commit).to be_persisted
expect(commit.builds.any?).to be false
expect(commit.status).to eq("skipped")
end
@@ -110,8 +110,9 @@ describe CreateCommitBuildsService, services: true do
before: '00000000',
after: '31das312',
commits: commits
- )
+ )
+ expect(commit).to be_persisted
expect(commit.builds.first.name).to eq("staging")
end
@@ -123,7 +124,8 @@ describe CreateCommitBuildsService, services: true do
before: '00000000',
after: '31das312',
commits: commits
- )
+ )
+ expect(commit).to be_persisted
expect(commit.builds.any?).to be false
expect(commit.status).to eq("skipped")
expect(commit.yaml_errors).to be_nil
@@ -139,7 +141,8 @@ describe CreateCommitBuildsService, services: true do
before: '00000000',
after: '31das312',
commits: commits
- )
+ )
+ expect(commit).to be_persisted
expect(commit.builds.count(:all)).to eq(2)
commit = service.execute(project, user,
@@ -147,7 +150,8 @@ describe CreateCommitBuildsService, services: true do
before: '00000000',
after: '31das312',
commits: commits
- )
+ )
+ expect(commit).to be_persisted
expect(commit.builds.count(:all)).to eq(2)
end
@@ -161,8 +165,9 @@ describe CreateCommitBuildsService, services: true do
before: '00000000',
after: '31das312',
commits: commits
- )
+ )
+ expect(commit).to be_persisted
expect(commit.status).to eq("failed")
expect(commit.builds.any?).to be false
end
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index a04c242cf0e..c1080ef190a 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -265,6 +265,75 @@ describe GitPushService, services: true do
expect(Issue.find(issue.id)).to be_opened
end
end
+
+ # EE-only tests
+ context "for jira issue tracker" do
+ include JiraServiceHelper
+
+ let(:jira_tracker) { project.create_jira_service if project.jira_service.nil? }
+
+ before do
+ jira_service_settings
+
+ WebMock.stub_request(:post, jira_api_transition_url)
+ WebMock.stub_request(:post, jira_api_comment_url)
+ WebMock.stub_request(:get, jira_api_comment_url).to_return(body: jira_issue_comments)
+ WebMock.stub_request(:get, jira_api_test_url)
+
+ allow(closing_commit).to receive_messages({
+ issue_closing_regex: Regexp.new(Gitlab.config.gitlab.issue_closing_pattern),
+ safe_message: message,
+ author_name: commit_author.name,
+ author_email: commit_author.email
+ })
+
+ allow(project.repository).to receive_messages(commits_between: [closing_commit])
+ end
+
+ after do
+ jira_tracker.destroy!
+ end
+
+ context "mentioning an issue" do
+ let(:message) { "this is some work.\n\nrelated to JIRA-1" }
+
+ it "should initiate one api call to jira server to mention the issue" do
+ service.execute(project, user, @oldrev, @newrev, @ref)
+
+ expect(WebMock).to have_requested(:post, jira_api_comment_url).with(
+ body: /mentioned this issue in/
+ ).once
+ end
+ end
+
+ context "closing an issue" do
+ let(:message) { "this is some work.\n\ncloses JIRA-1" }
+
+ it "should initiate one api call to jira server to close the issue" do
+ transition_body = {
+ transition: {
+ id: '2'
+ }
+ }.to_json
+
+ service.execute(project, user, @oldrev, @newrev, @ref)
+ expect(WebMock).to have_requested(:post, jira_api_transition_url).with(
+ body: transition_body
+ ).once
+ end
+
+ it "should initiate one api call to jira server to comment on the issue" do
+ comment_body = {
+ body: "Issue solved with [#{closing_commit.id}|http://localhost/#{project.path_with_namespace}/commit/#{closing_commit.id}]."
+ }.to_json
+
+ service.execute(project, user, @oldrev, @newrev, @ref)
+ expect(WebMock).to have_requested(:post, jira_api_comment_url).with(
+ body: comment_body
+ ).once
+ end
+ end
+ end
end
describe "empty project" do
diff --git a/spec/services/git_tag_push_service_spec.rb b/spec/services/git_tag_push_service_spec.rb
index e2d15f1a83d..b982274c529 100644
--- a/spec/services/git_tag_push_service_spec.rb
+++ b/spec/services/git_tag_push_service_spec.rb
@@ -58,14 +58,14 @@ describe GitTagPushService, services: true do
it { is_expected.to include(timestamp: @commit.date.xmlschema) }
it do
is_expected.to include(
- url: [
- Gitlab.config.gitlab.url,
- project.namespace.to_param,
- project.to_param,
- 'commit',
- @commit.id
- ].join('/')
- )
+ url: [
+ Gitlab.config.gitlab.url,
+ project.namespace.to_param,
+ project.to_param,
+ 'commit',
+ @commit.id
+ ].join('/')
+ )
end
context "with a author" do
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index d7a898e85ff..c103752198d 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -115,6 +115,7 @@ describe NotificationService, services: true do
before do
build_team(note.project)
+ note.project.team << [note.author, :master]
ActionMailer::Base.deliveries.clear
end
@@ -126,6 +127,8 @@ describe NotificationService, services: true do
note.project.team.members.each do |member|
# User with disabled notification should not be notified
next if member.id == @u_disabled.id
+ # Author should not be notified
+ next if member.id == note.author.id
should_email(member)
end
diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index c36d4581989..3c06a890163 100644
--- a/spec/services/projects/update_service_spec.rb
+++ b/spec/services/projects/update_service_spec.rb
@@ -100,6 +100,45 @@ describe Projects::UpdateService, services: true do
end
end
+ describe :visibility_level do
+ let(:user) { create :user, admin: true }
+ let(:project) { create :project, visibility_level: Gitlab::VisibilityLevel::INTERNAL }
+ let(:forked_project) { create :forked_project_with_submodules, visibility_level: Gitlab::VisibilityLevel::INTERNAL }
+ let(:opts) { {} }
+
+ before do
+ forked_project.build_forked_project_link(forked_to_project_id: forked_project.id, forked_from_project_id: project.id)
+ forked_project.save
+
+ @created_internal = project.internal?
+ @fork_created_internal = forked_project.internal?
+ end
+
+ context 'should update forks visibility level when parent set to more restrictive' do
+ before do
+ opts.merge!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ update_project(project, user, opts).inspect
+ end
+
+ it { expect(@created_internal).to be_truthy }
+ it { expect(@fork_created_internal).to be_truthy }
+ it { expect(project.private?).to be_truthy }
+ it { expect(project.forks.first.private?).to be_truthy }
+ end
+
+ context 'should not update forks visibility level when parent set to less restrictive' do
+ before do
+ opts.merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+ update_project(project, user, opts).inspect
+ end
+
+ it { expect(@created_internal).to be_truthy }
+ it { expect(@fork_created_internal).to be_truthy }
+ it { expect(project.public?).to be_truthy }
+ it { expect(project.forks.first.internal?).to be_truthy }
+ end
+ end
+
def update_project(project, user, opts)
Projects::UpdateService.new(project, user, opts).execute
end
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 0a4f9b230e8..c9f828ae2f7 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -425,4 +425,65 @@ describe SystemNoteService, services: true do
end
end
end
+
+ include JiraServiceHelper
+
+ describe 'JIRA integration' do
+ let(:project) { create(:project) }
+ let(:author) { create(:user) }
+ let(:issue) { create(:issue, project: project) }
+ let(:mergereq) { create(:merge_request, :simple, target_project: project, source_project: project) }
+ let(:jira_issue) { JiraIssue.new("JIRA-1", project)}
+ let(:jira_tracker) { project.create_jira_service if project.jira_service.nil? }
+ let(:commit) { project.commit }
+
+ context 'in JIRA issue tracker' do
+ before do
+ jira_service_settings
+ WebMock.stub_request(:post, jira_api_comment_url)
+ end
+
+ after do
+ jira_tracker.destroy!
+ end
+
+ describe "new reference" do
+ before do
+ WebMock.stub_request(:get, jira_api_comment_url).to_return(body: jira_issue_comments)
+ end
+
+ subject { described_class.cross_reference(jira_issue, commit, author) }
+
+ it { is_expected.to eq(jira_status_message) }
+ end
+
+ describe "existing reference" do
+ before do
+ message = "[#{author.name}|http://localhost/u/#{author.username}] mentioned this issue in [a commit of #{project.path_with_namespace}|http://localhost/#{project.path_with_namespace}/commit/#{commit.id}]."
+ WebMock.stub_request(:get, jira_api_comment_url).to_return(body: "{\"comments\":[{\"body\":\"#{message}\"}]}")
+ end
+
+ subject { described_class.cross_reference(jira_issue, commit, author) }
+ it { is_expected.not_to eq(jira_status_message) }
+ end
+ end
+
+ context 'issue from an issue' do
+ context 'in JIRA issue tracker' do
+ before do
+ jira_service_settings
+ WebMock.stub_request(:post, jira_api_comment_url)
+ WebMock.stub_request(:get, jira_api_comment_url).to_return(body: jira_issue_comments)
+ end
+
+ after do
+ jira_tracker.destroy!
+ end
+
+ subject { described_class.cross_reference(jira_issue, issue, author) }
+
+ it { is_expected.to eq(jira_status_message) }
+ end
+ end
+ end
end
diff --git a/spec/services/update_snippet_service_spec.rb b/spec/services/update_snippet_service_spec.rb
index 124bb76e678..48d114896d0 100644
--- a/spec/services/update_snippet_service_spec.rb
+++ b/spec/services/update_snippet_service_spec.rb
@@ -42,7 +42,7 @@ describe UpdateSnippetService, services: true do
CreateSnippetService.new(project, user, opts).execute
end
- def update_snippet(project = nil, user, snippet, opts)
+ def update_snippet(project, user, snippet, opts)
UpdateSnippetService.new(project, user, snippet, opts).execute
end
end
diff --git a/spec/support/jira_service_helper.rb b/spec/support/jira_service_helper.rb
new file mode 100644
index 00000000000..a3f496359b1
--- /dev/null
+++ b/spec/support/jira_service_helper.rb
@@ -0,0 +1,67 @@
+module JiraServiceHelper
+
+ def jira_service_settings
+ properties = {
+ "title"=>"JIRA tracker",
+ "project_url"=>"http://jira.example/issues/?jql=project=A",
+ "issues_url"=>"http://jira.example/browse/JIRA-1",
+ "new_issue_url"=>"http://jira.example/secure/CreateIssue.jspa",
+ "api_url"=>"http://jira.example/rest/api/2"
+ }
+
+ jira_tracker.update_attributes(properties: properties, active: true)
+ end
+
+ def jira_status_message
+ "JiraService SUCCESS 200: Successfully posted to #{jira_api_comment_url}."
+ end
+
+ def jira_issue_comments
+ "{\"startAt\":0,\"maxResults\":11,\"total\":11,
+ \"comments\":[{\"self\":\"http://0.0.0.0:4567/rest/api/2/issue/10002/comment/10609\",
+ \"id\":\"10609\",\"author\":{\"self\":\"http://0.0.0.0:4567/rest/api/2/user?username=gitlab\",
+ \"name\":\"gitlab\",\"emailAddress\":\"gitlab@example.com\",
+ \"avatarUrls\":{\"16x16\":\"http://0.0.0.0:4567/secure/useravatar?size=xsmall&avatarId=10122\",
+ \"24x24\":\"http://0.0.0.0:4567/secure/useravatar?size=small&avatarId=10122\",
+ \"32x32\":\"http://0.0.0.0:4567/secure/useravatar?size=medium&avatarId=10122\",
+ \"48x48\":\"http://0.0.0.0:4567/secure/useravatar?avatarId=10122\"},
+ \"displayName\":\"GitLab\",\"active\":true},
+ \"body\":\"[Administrator|http://localhost:3000/u/root] mentioned JIRA-1 in Merge request of [gitlab-org/gitlab-test|http://localhost:3000/gitlab-org/gitlab-test/merge_requests/2].\",
+ \"updateAuthor\":{\"self\":\"http://0.0.0.0:4567/rest/api/2/user?username=gitlab\",\"name\":\"gitlab\",\"emailAddress\":\"gitlab@example.com\",
+ \"avatarUrls\":{\"16x16\":\"http://0.0.0.0:4567/secure/useravatar?size=xsmall&avatarId=10122\",
+ \"24x24\":\"http://0.0.0.0:4567/secure/useravatar?size=small&avatarId=10122\",
+ \"32x32\":\"http://0.0.0.0:4567/secure/useravatar?size=medium&avatarId=10122\",
+ \"48x48\":\"http://0.0.0.0:4567/secure/useravatar?avatarId=10122\"},\"displayName\":\"GitLab\",\"active\":true},
+ \"created\":\"2015-02-12T22:47:07.826+0100\",
+ \"updated\":\"2015-02-12T22:47:07.826+0100\"},
+ {\"self\":\"http://0.0.0.0:4567/rest/api/2/issue/10002/comment/10700\",
+ \"id\":\"10700\",\"author\":{\"self\":\"http://0.0.0.0:4567/rest/api/2/user?username=gitlab\",
+ \"name\":\"gitlab\",\"emailAddress\":\"gitlab@example.com\",
+ \"avatarUrls\":{\"16x16\":\"http://0.0.0.0:4567/secure/useravatar?size=xsmall&avatarId=10122\",
+ \"24x24\":\"http://0.0.0.0:4567/secure/useravatar?size=small&avatarId=10122\",
+ \"32x32\":\"http://0.0.0.0:4567/secure/useravatar?size=medium&avatarId=10122\",
+ \"48x48\":\"http://0.0.0.0:4567/secure/useravatar?avatarId=10122\"},\"displayName\":\"GitLab\",\"active\":true},
+ \"body\":\"[Administrator|http://localhost:3000/u/root] mentioned this issue in [a commit of h5bp/html5-boilerplate|http://localhost:3000/h5bp/html5-boilerplate/commit/2439f77897122fbeee3bfd9bb692d3608848433e].\",
+ \"updateAuthor\":{\"self\":\"http://0.0.0.0:4567/rest/api/2/user?username=gitlab\",\"name\":\"gitlab\",\"emailAddress\":\"gitlab@example.com\",
+ \"avatarUrls\":{\"16x16\":\"http://0.0.0.0:4567/secure/useravatar?size=xsmall&avatarId=10122\",
+ \"24x24\":\"http://0.0.0.0:4567/secure/useravatar?size=small&avatarId=10122\",
+ \"32x32\":\"http://0.0.0.0:4567/secure/useravatar?size=medium&avatarId=10122\",
+ \"48x48\":\"http://0.0.0.0:4567/secure/useravatar?avatarId=10122\"},\"displayName\":\"GitLab\",\"active\":true},
+ \"created\":\"2015-04-01T03:45:55.667+0200\",
+ \"updated\":\"2015-04-01T03:45:55.667+0200\"
+ }
+ ]}"
+ end
+
+ def jira_api_comment_url
+ 'http://jira.example/rest/api/2/issue/JIRA-1/comment'
+ end
+
+ def jira_api_transition_url
+ 'http://jira.example/rest/api/2/issue/JIRA-1/transitions'
+ end
+
+ def jira_api_test_url
+ 'http://jira.example/rest/api/2/myself'
+ end
+end
diff --git a/spec/support/repo_helpers.rb b/spec/support/repo_helpers.rb
index aadf791bf3f..aa8258d6dad 100644
--- a/spec/support/repo_helpers.rb
+++ b/spec/support/repo_helpers.rb
@@ -45,12 +45,12 @@ eos
def another_sample_commit
OpenStruct.new(
- id: "e56497bb5f03a90a51293fc6d516788730953899",
- parent_id: '4cd80ccab63c82b4bad16faa5193fbd2aa06df40',
- author_full_name: "Sytse Sijbrandij",
- author_email: "sytse@gitlab.com",
- files_changed_count: 1,
- message: <<eos
+ id: "e56497bb5f03a90a51293fc6d516788730953899",
+ parent_id: '4cd80ccab63c82b4bad16faa5193fbd2aa06df40',
+ author_full_name: "Sytse Sijbrandij",
+ author_email: "sytse@gitlab.com",
+ files_changed_count: 1,
+ message: <<eos
Add directory structure for tree_helper spec
This directory structure is needed for a testing the method flatten_tree(tree) in the TreeHelper module
diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb
index 245f066df1f..dae31992620 100644
--- a/spec/workers/repository_fork_worker_spec.rb
+++ b/spec/workers/repository_fork_worker_spec.rb
@@ -9,20 +9,22 @@ describe RepositoryForkWorker do
describe "#perform" do
it "creates a new repository from a fork" do
expect_any_instance_of(Gitlab::Shell).to receive(:fork_repository).with(
- project.path_with_namespace,
- fork_project.namespace.path).
- and_return(true)
+ project.path_with_namespace,
+ fork_project.namespace.path
+ ).and_return(true)
- subject.perform(project.id,
- project.path_with_namespace,
- fork_project.namespace.path)
+ subject.perform(
+ project.id,
+ project.path_with_namespace,
+ fork_project.namespace.path)
end
it "handles bad fork" do
expect_any_instance_of(Gitlab::Shell).to receive(:fork_repository).and_return(false)
- subject.perform(project.id,
- project.path_with_namespace,
- fork_project.namespace.path)
+ subject.perform(
+ project.id,
+ project.path_with_namespace,
+ fork_project.namespace.path)
end
end
end
diff --git a/spec/workers/stuck_ci_builds_worker_spec.rb b/spec/workers/stuck_ci_builds_worker_spec.rb
index f9d87d97014..665ec20f224 100644
--- a/spec/workers/stuck_ci_builds_worker_spec.rb
+++ b/spec/workers/stuck_ci_builds_worker_spec.rb
@@ -15,7 +15,7 @@ describe StuckCiBuildsWorker do
end
it 'gets dropped if it was updated over 2 days ago' do
- build.update!(updated_at: 2.day.ago)
+ build.update!(updated_at: 2.days.ago)
StuckCiBuildsWorker.new.perform
is_expected.to eq('failed')
end
@@ -35,7 +35,7 @@ describe StuckCiBuildsWorker do
end
it "is still #{status}" do
- build.update!(updated_at: 2.day.ago)
+ build.update!(updated_at: 2.days.ago)
StuckCiBuildsWorker.new.perform
is_expected.to eq(status)
end